@shopify/hydrogen 0.26.0 → 0.26.1

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 (33) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/esnext/components/Link/Link.client.js +7 -4
  3. package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.js +2 -1
  4. package/dist/esnext/entry-server.js +19 -6
  5. package/dist/esnext/foundation/Router/BrowserRouter.client.d.ts +1 -1
  6. package/dist/esnext/foundation/Router/BrowserRouter.client.js +7 -5
  7. package/dist/esnext/foundation/useNavigate/useNavigate.js +8 -2
  8. package/dist/esnext/foundation/useUrl/useUrl.js +5 -3
  9. package/dist/esnext/framework/load-config.d.ts +0 -1
  10. package/dist/esnext/framework/load-config.js +1 -4
  11. package/dist/esnext/framework/plugin.js +1 -1
  12. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +2 -2
  13. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-rsc.d.ts +2 -1
  14. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-rsc.js +2 -1
  15. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-virtual-files.js +4 -21
  16. package/dist/esnext/framework/types.d.ts +1 -0
  17. package/dist/esnext/utilities/image_size.js +11 -0
  18. package/dist/esnext/version.d.ts +1 -1
  19. package/dist/esnext/version.js +1 -1
  20. package/dist/node/framework/load-config.d.ts +0 -1
  21. package/dist/node/framework/load-config.js +1 -4
  22. package/dist/node/framework/plugin.js +1 -1
  23. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +2 -2
  24. package/dist/node/framework/plugins/vite-plugin-hydrogen-rsc.d.ts +2 -1
  25. package/dist/node/framework/plugins/vite-plugin-hydrogen-rsc.js +2 -1
  26. package/dist/node/framework/plugins/vite-plugin-hydrogen-virtual-files.js +4 -21
  27. package/dist/node/framework/types.d.ts +1 -0
  28. package/package.json +1 -1
  29. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +26 -12
  30. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite.development.js +18 -3
  31. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite.production.min.js +2 -2
  32. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-plugin.js +26 -12
  33. package/vendor/react-server-dom-vite/esm/react-server-dom-vite.js +18 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.26.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1663](https://github.com/Shopify/hydrogen/pull/1663) [`66200d6b`](https://github.com/Shopify/hydrogen/commit/66200d6b7d8e54b0746a048e950f067d4b8e0609) Thanks [@jplhomer](https://github.com/jplhomer)! - Default to 'US' CountryCode if locale cannot be parsed correctly
8
+
9
+ * [#1690](https://github.com/Shopify/hydrogen/pull/1690) [`afde8989`](https://github.com/Shopify/hydrogen/commit/afde8989ae03e842de65ac698ab86033e56e7ee2) Thanks [@frehner](https://github.com/frehner)! - Add scale to the filename part of the url in `shopifyImageLoader()`
10
+
11
+ - [#1676](https://github.com/Shopify/hydrogen/pull/1676) [`0149cbf6`](https://github.com/Shopify/hydrogen/commit/0149cbf60b331461ae0c97bb3e18d3f27e143d0a) Thanks [@frandiox](https://github.com/frandiox)! - Avoid writing to Node response if it has been closed early.
12
+
13
+ * [#1674](https://github.com/Shopify/hydrogen/pull/1674) [`8068d3ce`](https://github.com/Shopify/hydrogen/commit/8068d3ce14f44ea83bde8f3729ae2a8cbbf8a52e) Thanks [@frandiox](https://github.com/frandiox)! - Throw error when `<Link>` component is used outside of `<Router>` component.
14
+
15
+ - [#1680](https://github.com/Shopify/hydrogen/pull/1680) [`acf5223f`](https://github.com/Shopify/hydrogen/commit/acf5223fe34cfdd483ae9b0e714445c8cbf11a9d) Thanks [@blittle](https://github.com/blittle)! - Fix basepath to not apply to external URLs in the `<Link` component. Also default the attribute `rel="noreferrer noopener` for external URLs.
16
+
17
+ * [#1670](https://github.com/Shopify/hydrogen/pull/1670) [`8b26f7a6`](https://github.com/Shopify/hydrogen/commit/8b26f7a6f034eaa36bb11974ff3dc5d992e2e97b) Thanks [@frandiox](https://github.com/frandiox)! - Optimize client boundaries only during build by default.
18
+
3
19
  ## 0.26.0
4
20
 
5
21
  ### Minor Changes
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
- import { useRouter } from '../../foundation/Router/BrowserRouter.client';
2
+ import { useLocation } from '../../foundation/Router/BrowserRouter.client';
3
3
  import { createPath } from 'history';
4
4
  import { buildPath, useNavigate } from '../../foundation/useNavigate/useNavigate';
5
5
  import { RSC_PATHNAME } from '../../constants';
@@ -12,7 +12,7 @@ import { useBasePath } from '../../foundation/useRouteParams/RouteParamsProvider
12
12
  */
13
13
  export const Link = React.forwardRef(function Link(props, ref) {
14
14
  const navigate = useNavigate();
15
- const { location } = useRouter();
15
+ const location = useLocation();
16
16
  const [_, startTransition] = React.useTransition();
17
17
  const routeBasePath = useBasePath();
18
18
  /**
@@ -98,12 +98,15 @@ export const Link = React.forwardRef(function Link(props, ref) {
98
98
  'reloadDocument',
99
99
  'prefetch',
100
100
  'scroll',
101
- ]), ref: ref, onClick: internalClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onFocus: onFocus, onBlur: onBlur, onTouchStart: onTouchStart, href: to }, props.children),
101
+ ]), ref: ref, rel: props.rel ??
102
+ (to.startsWith('http') || to.startsWith('//')
103
+ ? 'noreferrer noopener'
104
+ : undefined), onClick: internalClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onFocus: onFocus, onBlur: onBlur, onTouchStart: onTouchStart, href: to }, props.children),
102
105
  shouldPrefetch && React.createElement(Prefetch, { pathname: to })));
103
106
  });
104
107
  function Prefetch({ pathname }) {
105
108
  const { getProposedLocationServerProps } = useInternalServerProps();
106
- const { location } = useRouter();
109
+ const location = useLocation();
107
110
  const newPath = createPath({ pathname });
108
111
  if (pathname.startsWith('http') || newPath === createPath(location)) {
109
112
  return null;
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import LocalizationClientProvider from './LocalizationClientProvider.client';
3
3
  import { useShop } from '../../foundation/useShop';
4
4
  import { useServerRequest } from '../../foundation/ServerRequestProvider';
5
+ import { CountryCode } from '../../storefront-api-types';
5
6
  /**
6
7
  * The `LocalizationProvider` component automatically queries the Storefront API's
7
8
  * [`localization`](https://shopify.dev/api/storefront/reference/common-objects/queryroot) field
@@ -11,7 +12,7 @@ import { useServerRequest } from '../../foundation/ServerRequestProvider';
11
12
  */
12
13
  export function LocalizationProvider(props) {
13
14
  const { languageCode: defaultLanguageCode, locale } = useShop();
14
- const defaultCountryCode = locale.split(/[-_]/)[1] || '';
15
+ const defaultCountryCode = locale.split(/[-_]/)[1] || CountryCode.Us;
15
16
  const languageCode = (props.languageCode ?? defaultLanguageCode).toUpperCase();
16
17
  const countryCode = (props.countryCode ?? defaultCountryCode).toUpperCase();
17
18
  const request = useServerRequest();
@@ -337,11 +337,13 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
337
337
  startWritingToNodeResponse(nodeResponse, dev ? didError() : undefined);
338
338
  setTimeout(() => {
339
339
  log.trace('node pipe response');
340
- pipe(nodeResponse);
340
+ if (!nodeResponse.writableEnded)
341
+ pipe(nodeResponse);
341
342
  }, 0);
342
343
  bufferReadableStream(rscReadable.getReader(), (chunk) => {
343
344
  log.trace('rsc chunk');
344
- return nodeResponse.write(chunk);
345
+ if (!nodeResponse.writableEnded)
346
+ nodeResponse.write(chunk);
345
347
  });
346
348
  },
347
349
  async onAllReady() {
@@ -370,8 +372,10 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
370
372
  html = assembleHtml({ ssrHtml, rscPayload, request, template });
371
373
  postRequestTasks('ssr', nodeResponse.statusCode, request, response);
372
374
  }
373
- nodeResponse.write(html);
374
- nodeResponse.end();
375
+ if (!nodeResponse.writableEnded) {
376
+ nodeResponse.write(html);
377
+ nodeResponse.end();
378
+ }
375
379
  });
376
380
  pipe(bufferedResponse);
377
381
  },
@@ -385,8 +389,15 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
385
389
  }
386
390
  },
387
391
  onError(error) {
392
+ if (error.message?.includes('stream closed early')) {
393
+ // This seems to happen when Fizz is still streaming
394
+ // but nodeResponse has been closed by the browser.
395
+ // This is common in tests and during development
396
+ // due to frequent page refresh.
397
+ return;
398
+ }
388
399
  ssrDidError = error;
389
- if (dev && nodeResponse.headersSent) {
400
+ if (dev && nodeResponse.headersSent && !nodeResponse.writableEnded) {
390
401
  // Calling write would flush headers automatically.
391
402
  // Delay this error until headers are properly sent.
392
403
  nodeResponse.write(getErrorMarkup(error));
@@ -420,6 +431,8 @@ function runRSC({ App, state, log, request, response }) {
420
431
  }
421
432
  export default renderHydrogen;
422
433
  function startWritingToNodeResponse(nodeResponse, error) {
434
+ if (nodeResponse.writableEnded)
435
+ return;
423
436
  if (!nodeResponse.headersSent) {
424
437
  nodeResponse.setHeader(CONTENT_TYPE, HTML_CONTENT_TYPE);
425
438
  nodeResponse.write(DOCTYPE);
@@ -483,7 +496,7 @@ function postRequestTasks(type, status, request, response) {
483
496
  function handleFetchResponseInNode(fetchResponsePromise, nodeResponse) {
484
497
  if (nodeResponse) {
485
498
  fetchResponsePromise.then((response) => {
486
- if (!response)
499
+ if (!response || nodeResponse.writableEnded)
487
500
  return;
488
501
  setNodeHeaders(response.headers, nodeResponse);
489
502
  nodeResponse.statusCode = response.status;
@@ -4,7 +4,7 @@ declare type RouterContextValue = {
4
4
  history: BrowserHistory;
5
5
  location: Location;
6
6
  };
7
- export declare const RouterContext: React.Context<{} | RouterContextValue>;
7
+ export declare const RouterContext: React.Context<RouterContextValue | undefined>;
8
8
  export declare const BrowserRouter: FC<{
9
9
  history?: BrowserHistory;
10
10
  children: ReactNode;
@@ -2,7 +2,7 @@ import { createBrowserHistory } from 'history';
2
2
  import React, { createContext, useContext, useMemo, useState, useEffect, useLayoutEffect, useCallback, } from 'react';
3
3
  import { META_ENV_SSR } from '../ssr-interop';
4
4
  import { useInternalServerProps } from '../useServerProps/use-server-props';
5
- export const RouterContext = createContext({});
5
+ export const RouterContext = createContext(undefined);
6
6
  let isFirstLoad = true;
7
7
  const positions = {};
8
8
  export const BrowserRouter = ({ history: pHistory, children }) => {
@@ -51,11 +51,13 @@ export const BrowserRouter = ({ history: pHistory, children }) => {
51
51
  } }, children));
52
52
  };
53
53
  export function useRouter() {
54
+ if (META_ENV_SSR)
55
+ return { location: {}, history: {} };
56
+ // eslint-disable-next-line react-hooks/rules-of-hooks
54
57
  const router = useContext(RouterContext);
55
- if (!router && META_ENV_SSR) {
56
- throw new Error('useRouter must be used within a <Router> component');
57
- }
58
- return router;
58
+ if (router)
59
+ return router;
60
+ throw new Error('Router hooks and <Link> component must be used within a <Router> component');
59
61
  }
60
62
  export function useLocation() {
61
63
  return useRouter().location;
@@ -22,12 +22,18 @@ export function useNavigate() {
22
22
  };
23
23
  }
24
24
  export function buildPath(basePath, path) {
25
+ if (path.startsWith('http') || path.startsWith('//'))
26
+ return path;
25
27
  let builtPath = path;
26
28
  if (basePath !== '/') {
29
+ const pathFirstChar = path.charAt(0);
30
+ const basePathLastChar = basePath.charAt(basePath.length - 1);
27
31
  builtPath =
28
- path.charAt(0) === '/' && basePath.charAt(0) === '/'
32
+ pathFirstChar === '/' && basePathLastChar === '/'
29
33
  ? basePath + path.substring(1)
30
- : basePath + path;
34
+ : basePathLastChar !== '/' && pathFirstChar !== '/'
35
+ ? basePath + '/' + path
36
+ : basePath + path;
31
37
  }
32
38
  return builtPath;
33
39
  }
@@ -1,7 +1,7 @@
1
- import { useMemo } from 'react';
1
+ import { useContext, useMemo } from 'react';
2
2
  import { RSC_PATHNAME } from '../../constants';
3
3
  import { parseJSON } from '../../utilities/parse';
4
- import { useLocation } from '../Router/BrowserRouter.client';
4
+ import { RouterContext } from '../Router/BrowserRouter.client';
5
5
  import { useEnvContext, META_ENV_SSR } from '../ssr-interop';
6
6
  /**
7
7
  * The `useUrl` hook retrieves the current URL in a server or client component.
@@ -20,8 +20,10 @@ export function useUrl() {
20
20
  /**
21
21
  * We return a `URL` object instead of passing through `location` because
22
22
  * the URL object contains important info like hostname, etc.
23
+ * Note: do not call `useLocation` directly here to avoid throwing errors
24
+ * when `useUrl` is used outside of a Router component (e.g. in <Seo>).
23
25
  */
24
- const location = useLocation(); // eslint-disable-line react-hooks/rules-of-hooks
26
+ const location = useContext(RouterContext); // eslint-disable-line react-hooks/rules-of-hooks
25
27
  // eslint-disable-next-line react-hooks/rules-of-hooks
26
28
  return useMemo(() => new URL(window.location.href), [location]); // eslint-disable-line react-hooks/exhaustive-deps
27
29
  }
@@ -2,5 +2,4 @@ export declare function loadConfig(options?: {
2
2
  root: string;
3
3
  }): Promise<{
4
4
  configuration: any;
5
- configurationPath: any;
6
5
  }>;
@@ -3,8 +3,5 @@ import { VIRTUAL_PROXY_HYDROGEN_CONFIG_ID } from './plugins/vite-plugin-hydrogen
3
3
  import { viteception } from './viteception';
4
4
  export async function loadConfig(options = { root: process.cwd() }) {
5
5
  const { loaded } = await viteception([VIRTUAL_PROXY_HYDROGEN_CONFIG_ID], options);
6
- return {
7
- configuration: loaded[0].default,
8
- configurationPath: loaded[0].configPath,
9
- };
6
+ return { configuration: loaded[0].default };
10
7
  }
@@ -24,7 +24,7 @@ const hydrogenPlugin = (pluginOptions = {}) => {
24
24
  hydrationAutoImport(),
25
25
  ssrInterop(),
26
26
  cssModulesRsc(),
27
- rsc(),
27
+ rsc(pluginOptions),
28
28
  platformEntry(),
29
29
  suppressWarnings(),
30
30
  pluginOptions.purgeQueryCacheOnBuild && purgeQueryCache(),
@@ -12,7 +12,7 @@ export default () => {
12
12
  format: 'es',
13
13
  };
14
14
  }
15
- if (process.env.NODE_ENV !== 'development') {
15
+ if (process.env.NODE_ENV !== 'development' && !process.env.LOCAL_DEV) {
16
16
  /**
17
17
  * Ofuscate production asset name - To prevent ad blocker logics that blocks
18
18
  * certain files due to how it is named.
@@ -32,7 +32,7 @@ export default () => {
32
32
  },
33
33
  },
34
34
  build: {
35
- minify: config.build?.minify ?? 'esbuild',
35
+ minify: config.build?.minify ?? (process.env.LOCAL_DEV ? false : 'esbuild'),
36
36
  sourcemap: true,
37
37
  rollupOptions: config.build?.rollupOptions
38
38
  ? Object.assign(rollupOptions, config.build.rollupOptions)
@@ -1 +1,2 @@
1
- export default function (): any;
1
+ import { HydrogenVitePluginOptions } from '../types';
2
+ export default function (options?: HydrogenVitePluginOptions): any;
@@ -2,7 +2,7 @@
2
2
  import reactServerDomVite from '@shopify/hydrogen/vendor/react-server-dom-vite/plugin.js';
3
3
  import { HYDROGEN_DEFAULT_SERVER_ENTRY } from './vite-plugin-hydrogen-middleware';
4
4
  import { VIRTUAL_PROXY_HYDROGEN_ROUTES_ID } from './vite-plugin-hydrogen-virtual-files';
5
- export default function () {
5
+ export default function (options) {
6
6
  return reactServerDomVite({
7
7
  serverBuildEntries: [
8
8
  HYDROGEN_DEFAULT_SERVER_ENTRY,
@@ -18,5 +18,6 @@ export default function () {
18
18
  // TODO: revisit this when RSC splits into two bundles
19
19
  /\.test\.[tj]sx?$/.test(importer));
20
20
  },
21
+ ...options,
21
22
  });
22
23
  }
@@ -15,25 +15,20 @@ export const VIRTUAL_PROXY_HYDROGEN_ROUTES_ID = VIRTUAL_PREFIX + PROXY_PREFIX +
15
15
  export default (pluginOptions) => {
16
16
  let config;
17
17
  let server;
18
- let resolvedConfigPath;
19
18
  return {
20
19
  name: 'hydrogen:virtual-files',
21
20
  configResolved(_config) {
22
21
  config = _config;
23
- // @ts-ignore
24
- config.plugins.push(addPathToConfigProxy());
25
22
  },
26
23
  configureServer(_server) {
27
24
  server = _server;
28
25
  },
29
26
  resolveId(source, importer) {
30
27
  if (source === VIRTUAL_HYDROGEN_CONFIG_ID) {
31
- return findHydrogenConfigPath(config.root, pluginOptions.configPath).then((hcPath) => {
32
- resolvedConfigPath = hcPath;
33
- // This direct dependency on a real file
34
- // makes HMR work for the virtual module.
35
- return this.resolve(hcPath, importer, { skipSelf: true });
36
- });
28
+ return findHydrogenConfigPath(config.root, pluginOptions.configPath).then((hcPath) =>
29
+ // This direct dependency on a real file
30
+ // makes HMR work for the virtual module.
31
+ this.resolve(hcPath, importer, { skipSelf: true }));
37
32
  }
38
33
  if ([
39
34
  VIRTUAL_PROXY_HYDROGEN_CONFIG_ID,
@@ -86,18 +81,6 @@ export default (pluginOptions) => {
86
81
  const { loaded } = await viteception([VIRTUAL_PROXY_HYDROGEN_CONFIG_ID]);
87
82
  return loaded[0].default;
88
83
  }
89
- function addPathToConfigProxy() {
90
- return {
91
- name: 'hydrogen:virtual-files-post',
92
- enforce: 'post',
93
- transform(code, id) {
94
- if (id === '\0' + VIRTUAL_PROXY_HYDROGEN_CONFIG_ID) {
95
- // The CLI needs to import the Hydrogen config path
96
- return (code + ` export const configPath = '${resolvedConfigPath || ''}';`);
97
- }
98
- },
99
- };
100
- }
101
84
  };
102
85
  async function findHydrogenConfigPath(root, userProvidedPath) {
103
86
  let configPath = userProvidedPath;
@@ -2,4 +2,5 @@ export interface HydrogenVitePluginOptions {
2
2
  devCache?: boolean;
3
3
  purgeQueryCacheOnBuild?: boolean;
4
4
  configPath?: string;
5
+ optimizeBoundaries?: boolean | 'build';
5
6
  }
@@ -11,6 +11,17 @@ const ALL_CDN_HOSTNAMES = [...PRODUCTION_CDN_HOSTNAMES, ...LOCAL_CDN_HOSTNAMES];
11
11
  * Adds image size parameters to an image URL hosted by Shopify's CDN
12
12
  */
13
13
  export function addImageSizeParametersToUrl({ src, width, height, crop, scale, }) {
14
+ if (scale) {
15
+ // Have to do this specifically for 'scale' because it doesn't currently work otherwise.
16
+ // I'm also intentionally leaving 'scale' as a searchParam because that way it'll "just work" in the future and we can just delete this whole section of code
17
+ // We assume here that the last `.` is the delimiter between the file name and the file type
18
+ const baseUrl = new URL(src);
19
+ const fileDelimiterIndex = baseUrl.pathname.lastIndexOf('.');
20
+ const fileName = baseUrl.pathname.slice(0, fileDelimiterIndex);
21
+ const fileType = baseUrl.pathname.slice(fileDelimiterIndex);
22
+ baseUrl.pathname = `${fileName}${`@${scale.toString()}x`}${fileType}`;
23
+ src = baseUrl.toString();
24
+ }
14
25
  const newUrl = new URL(src);
15
26
  width && newUrl.searchParams.append('width', width.toString());
16
27
  height && newUrl.searchParams.append('height', height.toString());
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "0.26.0";
1
+ export declare const LIB_VERSION = "0.26.1";
@@ -1 +1 @@
1
- export const LIB_VERSION = '0.26.0';
1
+ export const LIB_VERSION = '0.26.1';
@@ -2,5 +2,4 @@ export declare function loadConfig(options?: {
2
2
  root: string;
3
3
  }): Promise<{
4
4
  configuration: any;
5
- configurationPath: any;
6
5
  }>;
@@ -6,9 +6,6 @@ const vite_plugin_hydrogen_virtual_files_1 = require("./plugins/vite-plugin-hydr
6
6
  const viteception_1 = require("./viteception");
7
7
  async function loadConfig(options = { root: process.cwd() }) {
8
8
  const { loaded } = await (0, viteception_1.viteception)([vite_plugin_hydrogen_virtual_files_1.VIRTUAL_PROXY_HYDROGEN_CONFIG_ID], options);
9
- return {
10
- configuration: loaded[0].default,
11
- configurationPath: loaded[0].configPath,
12
- };
9
+ return { configuration: loaded[0].default };
13
10
  }
14
11
  exports.loadConfig = loadConfig;
@@ -28,7 +28,7 @@ const hydrogenPlugin = (pluginOptions = {}) => {
28
28
  (0, vite_plugin_hydration_auto_import_1.default)(),
29
29
  (0, vite_plugin_ssr_interop_1.default)(),
30
30
  (0, vite_plugin_css_modules_rsc_1.default)(),
31
- (0, vite_plugin_hydrogen_rsc_1.default)(),
31
+ (0, vite_plugin_hydrogen_rsc_1.default)(pluginOptions),
32
32
  (0, vite_plugin_platform_entry_1.default)(),
33
33
  (0, vite_plugin_hydrogen_suppress_warnings_1.default)(),
34
34
  pluginOptions.purgeQueryCacheOnBuild && (0, vite_plugin_purge_query_cache_1.default)(),
@@ -14,7 +14,7 @@ exports.default = () => {
14
14
  format: 'es',
15
15
  };
16
16
  }
17
- if (process.env.NODE_ENV !== 'development') {
17
+ if (process.env.NODE_ENV !== 'development' && !process.env.LOCAL_DEV) {
18
18
  /**
19
19
  * Ofuscate production asset name - To prevent ad blocker logics that blocks
20
20
  * certain files due to how it is named.
@@ -34,7 +34,7 @@ exports.default = () => {
34
34
  },
35
35
  },
36
36
  build: {
37
- minify: config.build?.minify ?? 'esbuild',
37
+ minify: config.build?.minify ?? (process.env.LOCAL_DEV ? false : 'esbuild'),
38
38
  sourcemap: true,
39
39
  rollupOptions: config.build?.rollupOptions
40
40
  ? Object.assign(rollupOptions, config.build.rollupOptions)
@@ -1 +1,2 @@
1
- export default function (): any;
1
+ import { HydrogenVitePluginOptions } from '../types';
2
+ export default function (options?: HydrogenVitePluginOptions): any;
@@ -7,7 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const plugin_js_1 = __importDefault(require("@shopify/hydrogen/vendor/react-server-dom-vite/plugin.js"));
8
8
  const vite_plugin_hydrogen_middleware_1 = require("./vite-plugin-hydrogen-middleware");
9
9
  const vite_plugin_hydrogen_virtual_files_1 = require("./vite-plugin-hydrogen-virtual-files");
10
- function default_1() {
10
+ function default_1(options) {
11
11
  return (0, plugin_js_1.default)({
12
12
  serverBuildEntries: [
13
13
  vite_plugin_hydrogen_middleware_1.HYDROGEN_DEFAULT_SERVER_ENTRY,
@@ -23,6 +23,7 @@ function default_1() {
23
23
  // TODO: revisit this when RSC splits into two bundles
24
24
  /\.test\.[tj]sx?$/.test(importer));
25
25
  },
26
+ ...options,
26
27
  });
27
28
  }
28
29
  exports.default = default_1;
@@ -21,25 +21,20 @@ exports.VIRTUAL_PROXY_HYDROGEN_ROUTES_ID = VIRTUAL_PREFIX + PROXY_PREFIX + HYDRO
21
21
  exports.default = (pluginOptions) => {
22
22
  let config;
23
23
  let server;
24
- let resolvedConfigPath;
25
24
  return {
26
25
  name: 'hydrogen:virtual-files',
27
26
  configResolved(_config) {
28
27
  config = _config;
29
- // @ts-ignore
30
- config.plugins.push(addPathToConfigProxy());
31
28
  },
32
29
  configureServer(_server) {
33
30
  server = _server;
34
31
  },
35
32
  resolveId(source, importer) {
36
33
  if (source === VIRTUAL_HYDROGEN_CONFIG_ID) {
37
- return findHydrogenConfigPath(config.root, pluginOptions.configPath).then((hcPath) => {
38
- resolvedConfigPath = hcPath;
39
- // This direct dependency on a real file
40
- // makes HMR work for the virtual module.
41
- return this.resolve(hcPath, importer, { skipSelf: true });
42
- });
34
+ return findHydrogenConfigPath(config.root, pluginOptions.configPath).then((hcPath) =>
35
+ // This direct dependency on a real file
36
+ // makes HMR work for the virtual module.
37
+ this.resolve(hcPath, importer, { skipSelf: true }));
43
38
  }
44
39
  if ([
45
40
  exports.VIRTUAL_PROXY_HYDROGEN_CONFIG_ID,
@@ -92,18 +87,6 @@ exports.default = (pluginOptions) => {
92
87
  const { loaded } = await (0, viteception_1.viteception)([exports.VIRTUAL_PROXY_HYDROGEN_CONFIG_ID]);
93
88
  return loaded[0].default;
94
89
  }
95
- function addPathToConfigProxy() {
96
- return {
97
- name: 'hydrogen:virtual-files-post',
98
- enforce: 'post',
99
- transform(code, id) {
100
- if (id === '\0' + exports.VIRTUAL_PROXY_HYDROGEN_CONFIG_ID) {
101
- // The CLI needs to import the Hydrogen config path
102
- return (code + ` export const configPath = '${resolvedConfigPath || ''}';`);
103
- }
104
- },
105
- };
106
- }
107
90
  };
108
91
  async function findHydrogenConfigPath(root, userProvidedPath) {
109
92
  let configPath = userProvidedPath;
@@ -2,4 +2,5 @@ export interface HydrogenVitePluginOptions {
2
2
  devCache?: boolean;
3
3
  purgeQueryCacheOnBuild?: boolean;
4
4
  configPath?: string;
5
+ optimizeBoundaries?: boolean | 'build';
5
6
  }
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "engines": {
8
8
  "node": ">=14"
9
9
  },
10
- "version": "0.26.0",
10
+ "version": "0.26.1",
11
11
  "description": "Modern custom Shopify storefronts",
12
12
  "license": "MIT",
13
13
  "main": "dist/esnext/index.js",
@@ -104,6 +104,8 @@ var isClientComponent = function (id) {
104
104
  function ReactFlightVitePlugin() {
105
105
  var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
106
106
  serverBuildEntries = _ref.serverBuildEntries,
107
+ _ref$optimizeBoundari = _ref.optimizeBoundaries,
108
+ optimizeBoundaries = _ref$optimizeBoundari === void 0 ? 'build' : _ref$optimizeBoundari,
107
109
  _ref$isServerComponen = _ref.isServerComponentImporterAllowed,
108
110
  isServerComponentImporterAllowed = _ref$isServerComponen === void 0 ? function (importer) {
109
111
  return false;
@@ -114,11 +116,25 @@ function ReactFlightVitePlugin() {
114
116
  var resolveAlias;
115
117
  var globImporterPath;
116
118
  var allClientBoundaries = new Set();
119
+
120
+ function invalidateGlobImporter() {
121
+ if (globImporterPath && server) {
122
+ server.watcher.emit('change', globImporterPath);
123
+ }
124
+ }
125
+
117
126
  return {
118
127
  name: 'vite-plugin-react-server-components',
119
128
  enforce: 'pre',
120
129
  configureServer: function (_server) {
121
130
  server = _server;
131
+ var seenModules = {};
132
+ server.ws.on('rsc:cc404', function (data) {
133
+ if (!seenModules[data.id]) {
134
+ seenModules[data.id] = true;
135
+ invalidateGlobImporter();
136
+ }
137
+ });
122
138
  },
123
139
  configResolved: async function (_config) {
124
140
  await esModuleLexer.init;
@@ -195,13 +211,10 @@ function ReactFlightVitePlugin() {
195
211
  if (!moduleNode.meta) moduleNode.meta = {};
196
212
 
197
213
  if (!moduleNode.meta.isClientComponent) {
198
- moduleNode.meta.isClientComponent = true;
214
+ moduleNode.meta.isClientComponent = true; // Invalidate glob importer file to account for the
215
+ // newly discovered client component.
199
216
 
200
- if (globImporterPath) {
201
- // Invalidate glob importer file to account for the
202
- // newly discovered client component.
203
- server.watcher.emit('change', globImporterPath);
204
- }
217
+ invalidateGlobImporter();
205
218
  }
206
219
  }
207
220
 
@@ -272,7 +285,7 @@ function ReactFlightVitePlugin() {
272
285
  // reuse the already discovered ones and simply add new ones
273
286
  // to the list without removing anything.
274
287
 
275
- findClientBoundaries(server.moduleGraph).forEach(function (boundary) {
288
+ findClientBoundaries(server.moduleGraph, optimizeBoundaries === true).forEach(function (boundary) {
276
289
  return allClientBoundaries.add(boundary);
277
290
  });
278
291
  return injectGlobs(Array.from(allClientBoundaries));
@@ -282,7 +295,7 @@ function ReactFlightVitePlugin() {
282
295
  throw new Error('[react-server-dom-vite] Parameter serverBuildEntries is required for client build');
283
296
  }
284
297
 
285
- return findClientBoundariesForClientBuild(serverBuildEntries).then(injectGlobs);
298
+ return findClientBoundariesForClientBuild(serverBuildEntries, optimizeBoundaries !== false).then(injectGlobs);
286
299
  }
287
300
  }
288
301
  };
@@ -346,6 +359,7 @@ async function proxyClientComponent(filepath, src) {
346
359
  }
347
360
 
348
361
  function findClientBoundaries(moduleGraph) {
362
+ var optimizeBoundaries = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
349
363
  var clientBoundaries = []; // eslint-disable-next-line no-for-of-loops/no-for-of-loops
350
364
 
351
365
  var _iterator = _createForOfIteratorHelper(moduleGraph.fileToModulesMap.values()),
@@ -358,7 +372,7 @@ function findClientBoundaries(moduleGraph) {
358
372
  return moduleNode.meta && moduleNode.meta.isClientComponent;
359
373
  });
360
374
 
361
- if (clientModule && isDirectImportInServer(clientModule)) {
375
+ if (clientModule && (!optimizeBoundaries || isDirectImportInServer(clientModule))) {
362
376
  clientBoundaries.push(clientModule.file);
363
377
  }
364
378
  }
@@ -371,7 +385,7 @@ function findClientBoundaries(moduleGraph) {
371
385
  return clientBoundaries;
372
386
  }
373
387
 
374
- async function findClientBoundariesForClientBuild(serverEntries) {
388
+ async function findClientBoundariesForClientBuild(serverEntries, optimizeBoundaries) {
375
389
  // Viteception
376
390
  var server = await vite.createServer({
377
391
  clearScreen: false,
@@ -389,7 +403,7 @@ async function findClientBoundariesForClientBuild(serverEntries) {
389
403
  }
390
404
 
391
405
  await server.close();
392
- return findClientBoundaries(server.moduleGraph);
406
+ return findClientBoundaries(server.moduleGraph, optimizeBoundaries);
393
407
  }
394
408
 
395
409
  var hashImportsPlugin = {
@@ -487,7 +501,7 @@ function isDirectImportInServer(originalMod, currentMod, accModInfo) {
487
501
  // the original module before marking it as client boundary.
488
502
 
489
503
  return currentMod.meta.imports.some(function (imp) {
490
- return imp.action === 'import' && imp.from === accModInfo.file && (imp.variables || []).some(function (_ref3) {
504
+ return imp.from === accModInfo.file && (imp.variables || []).some(function (_ref3) {
491
505
  var name = _ref3[0];
492
506
  return accModInfo.exports.includes(name);
493
507
  });
@@ -33,7 +33,9 @@ function parseModel(response, json) {
33
33
  return JSON.parse(json, response._fromJSON);
34
34
  }
35
35
 
36
- // eslint-disable-next-line no-unused-vars
36
+ var META_HOT = undefined;
37
+ var META_ENV_DEV = undefined.DEV;
38
+
37
39
  function resolveModuleReference(bundlerConfig, moduleData) {
38
40
  return moduleData;
39
41
  } // Vite import globs will be injected here.
@@ -42,7 +44,7 @@ var allClientComponents = {
42
44
  __INJECTED_CLIENT_IMPORTERS__: null
43
45
  }; // Mock client component imports during testing
44
46
 
45
- if (typeof jest !== 'undefined') {
47
+ if (META_ENV_DEV && typeof jest !== 'undefined') {
46
48
  global.allClientComponents = allClientComponents;
47
49
  }
48
50
 
@@ -50,7 +52,20 @@ function importClientComponent(moduleId) {
50
52
  var modImport = allClientComponents[moduleId];
51
53
 
52
54
  if (!modImport) {
53
- return Promise.reject(new Error("Could not find client component " + moduleId));
55
+ var error = new Error("Could not find client component " + moduleId);
56
+
57
+ if (META_HOT) {
58
+ META_HOT.send('rsc:cc404', {
59
+ id: moduleId
60
+ });
61
+ return new Promise(function (_, reject) {
62
+ return setTimeout(function () {
63
+ return reject(error);
64
+ }, 200);
65
+ });
66
+ }
67
+
68
+ return Promise.reject(error);
54
69
  }
55
70
 
56
71
  return typeof modImport === 'function' ? modImport() : Promise.resolve(modImport);
@@ -7,8 +7,8 @@
7
7
  * This source code is licensed under the MIT license found in the
8
8
  * LICENSE file in the root directory of this source tree.
9
9
  */
10
- 'use strict';var g=require("react"),h={stream:!0},l={__INJECTED_CLIENT_IMPORTERS__:null};"undefined"!==typeof jest&&(global.allClientComponents=l);function m(a){var b=l[a];return b?"function"===typeof b?b():Promise.resolve(b):Promise.reject(Error("Could not find client component "+a))}var n=new Map;function p(a){function b(a){n.set(c,a);return a}var c=a.id;n.has(c)||(a=m(c),b(a),a.then(b,b))}var q=Symbol.for("react.element"),r=Symbol.for("react.lazy"),t=Symbol.for("react.default_value"),v=g.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ContextRegistry;
11
- function w(a){v[a]||(v[a]=g.createServerContext(a,t));return v[a]}function x(a,b,c){this._status=a;this._value=b;this._response=c}x.prototype.then=function(a){0===this._status?(null===this._value&&(this._value=[]),this._value.push(a)):a()};
10
+ 'use strict';var g=require("react"),h={stream:!0},l={__INJECTED_CLIENT_IMPORTERS__:null};(void 0).DEV&&"undefined"!==typeof jest&&(global.allClientComponents=l);function m(a){var b=l[a];return b?"function"===typeof b?b():Promise.resolve(b):Promise.reject(Error("Could not find client component "+a))}var n=new Map;function p(a){function b(a){n.set(c,a);return a}var c=a.id;n.has(c)||(a=m(c),b(a),a.then(b,b))}
11
+ var q=Symbol.for("react.element"),r=Symbol.for("react.lazy"),t=Symbol.for("react.default_value"),v=g.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ContextRegistry;function w(a){v[a]||(v[a]=g.createServerContext(a,t));return v[a]}function x(a,b,c){this._status=a;this._value=b;this._response=c}x.prototype.then=function(a){0===this._status?(null===this._value&&(this._value=[]),this._value.push(a)):a()};
12
12
  function y(a){switch(a._status){case 3:return a._value;case 1:var b=JSON.parse(a._value,a._response._fromJSON);a._status=3;return a._value=b;case 2:var c=a._value;b=c.name;c=n.get(c.id);if(!c||c instanceof Promise||c instanceof Error)throw c;b=c[b];a._status=3;return a._value=b;case 0:throw a;default:throw a._value;}}function z(){var a=A(this,0);return y(a)}function B(a,b){return new x(3,b,a)}function C(a){if(null!==a)for(var b=0;b<a.length;b++)(0,a[b])()}
13
13
  function D(a,b){if(0===a._status){var c=a._value;a._status=4;a._value=b;C(c)}}function E(a,b){a._chunks.forEach(function(a){D(a,b)})}function A(a,b){var c=a._chunks,d=c.get(b);d||(d=new x(0,null,a),c.set(b,d));return d}function F(a,b,c){switch(c[0]){case "$":if("$"===c)return q;if("$"===c[1]||"@"===c[1])return c.substring(1);b=parseInt(c.substring(1),16);a=A(a,b);return y(a);case "@":return b=parseInt(c.substring(1),16),a=A(a,b),{$$typeof:r,_payload:a,_init:y}}return c}
14
14
  function G(a){E(a,Error("Connection closed."))}
@@ -100,6 +100,8 @@ var isClientComponent = function (id) {
100
100
  function ReactFlightVitePlugin() {
101
101
  var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
102
102
  serverBuildEntries = _ref.serverBuildEntries,
103
+ _ref$optimizeBoundari = _ref.optimizeBoundaries,
104
+ optimizeBoundaries = _ref$optimizeBoundari === void 0 ? 'build' : _ref$optimizeBoundari,
103
105
  _ref$isServerComponen = _ref.isServerComponentImporterAllowed,
104
106
  isServerComponentImporterAllowed = _ref$isServerComponen === void 0 ? function (importer) {
105
107
  return false;
@@ -110,11 +112,25 @@ function ReactFlightVitePlugin() {
110
112
  var resolveAlias;
111
113
  var globImporterPath;
112
114
  var allClientBoundaries = new Set();
115
+
116
+ function invalidateGlobImporter() {
117
+ if (globImporterPath && server) {
118
+ server.watcher.emit('change', globImporterPath);
119
+ }
120
+ }
121
+
113
122
  return {
114
123
  name: 'vite-plugin-react-server-components',
115
124
  enforce: 'pre',
116
125
  configureServer: function (_server) {
117
126
  server = _server;
127
+ var seenModules = {};
128
+ server.ws.on('rsc:cc404', function (data) {
129
+ if (!seenModules[data.id]) {
130
+ seenModules[data.id] = true;
131
+ invalidateGlobImporter();
132
+ }
133
+ });
118
134
  },
119
135
  configResolved: async function (_config) {
120
136
  await init;
@@ -191,13 +207,10 @@ function ReactFlightVitePlugin() {
191
207
  if (!moduleNode.meta) moduleNode.meta = {};
192
208
 
193
209
  if (!moduleNode.meta.isClientComponent) {
194
- moduleNode.meta.isClientComponent = true;
210
+ moduleNode.meta.isClientComponent = true; // Invalidate glob importer file to account for the
211
+ // newly discovered client component.
195
212
 
196
- if (globImporterPath) {
197
- // Invalidate glob importer file to account for the
198
- // newly discovered client component.
199
- server.watcher.emit('change', globImporterPath);
200
- }
213
+ invalidateGlobImporter();
201
214
  }
202
215
  }
203
216
 
@@ -268,7 +281,7 @@ function ReactFlightVitePlugin() {
268
281
  // reuse the already discovered ones and simply add new ones
269
282
  // to the list without removing anything.
270
283
 
271
- findClientBoundaries(server.moduleGraph).forEach(function (boundary) {
284
+ findClientBoundaries(server.moduleGraph, optimizeBoundaries === true).forEach(function (boundary) {
272
285
  return allClientBoundaries.add(boundary);
273
286
  });
274
287
  return injectGlobs(Array.from(allClientBoundaries));
@@ -278,7 +291,7 @@ function ReactFlightVitePlugin() {
278
291
  throw new Error('[react-server-dom-vite] Parameter serverBuildEntries is required for client build');
279
292
  }
280
293
 
281
- return findClientBoundariesForClientBuild(serverBuildEntries).then(injectGlobs);
294
+ return findClientBoundariesForClientBuild(serverBuildEntries, optimizeBoundaries !== false).then(injectGlobs);
282
295
  }
283
296
  }
284
297
  };
@@ -342,6 +355,7 @@ async function proxyClientComponent(filepath, src) {
342
355
  }
343
356
 
344
357
  function findClientBoundaries(moduleGraph) {
358
+ var optimizeBoundaries = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
345
359
  var clientBoundaries = []; // eslint-disable-next-line no-for-of-loops/no-for-of-loops
346
360
 
347
361
  var _iterator = _createForOfIteratorHelper(moduleGraph.fileToModulesMap.values()),
@@ -354,7 +368,7 @@ function findClientBoundaries(moduleGraph) {
354
368
  return moduleNode.meta && moduleNode.meta.isClientComponent;
355
369
  });
356
370
 
357
- if (clientModule && isDirectImportInServer(clientModule)) {
371
+ if (clientModule && (!optimizeBoundaries || isDirectImportInServer(clientModule))) {
358
372
  clientBoundaries.push(clientModule.file);
359
373
  }
360
374
  }
@@ -367,7 +381,7 @@ function findClientBoundaries(moduleGraph) {
367
381
  return clientBoundaries;
368
382
  }
369
383
 
370
- async function findClientBoundariesForClientBuild(serverEntries) {
384
+ async function findClientBoundariesForClientBuild(serverEntries, optimizeBoundaries) {
371
385
  // Viteception
372
386
  var server = await createServer({
373
387
  clearScreen: false,
@@ -385,7 +399,7 @@ async function findClientBoundariesForClientBuild(serverEntries) {
385
399
  }
386
400
 
387
401
  await server.close();
388
- return findClientBoundaries(server.moduleGraph);
402
+ return findClientBoundaries(server.moduleGraph, optimizeBoundaries);
389
403
  }
390
404
 
391
405
  var hashImportsPlugin = {
@@ -483,7 +497,7 @@ function isDirectImportInServer(originalMod, currentMod, accModInfo) {
483
497
  // the original module before marking it as client boundary.
484
498
 
485
499
  return currentMod.meta.imports.some(function (imp) {
486
- return imp.action === 'import' && imp.from === accModInfo.file && (imp.variables || []).some(function (_ref3) {
500
+ return imp.from === accModInfo.file && (imp.variables || []).some(function (_ref3) {
487
501
  var name = _ref3[0];
488
502
  return accModInfo.exports.includes(name);
489
503
  });
@@ -27,7 +27,9 @@ function parseModel(response, json) {
27
27
  return JSON.parse(json, response._fromJSON);
28
28
  }
29
29
 
30
- // eslint-disable-next-line no-unused-vars
30
+ var META_HOT = import.meta.hot;
31
+ var META_ENV_DEV = import.meta.env.DEV;
32
+
31
33
  function resolveModuleReference(bundlerConfig, moduleData) {
32
34
  return moduleData;
33
35
  } // Vite import globs will be injected here.
@@ -36,7 +38,7 @@ var allClientComponents = {
36
38
  __INJECTED_CLIENT_IMPORTERS__: null
37
39
  }; // Mock client component imports during testing
38
40
 
39
- if (typeof jest !== 'undefined') {
41
+ if (META_ENV_DEV && typeof jest !== 'undefined') {
40
42
  global.allClientComponents = allClientComponents;
41
43
  }
42
44
 
@@ -44,7 +46,20 @@ function importClientComponent(moduleId) {
44
46
  var modImport = allClientComponents[moduleId];
45
47
 
46
48
  if (!modImport) {
47
- return Promise.reject(new Error("Could not find client component " + moduleId));
49
+ var error = new Error("Could not find client component " + moduleId);
50
+
51
+ if (META_HOT) {
52
+ META_HOT.send('rsc:cc404', {
53
+ id: moduleId
54
+ });
55
+ return new Promise(function (_, reject) {
56
+ return setTimeout(function () {
57
+ return reject(error);
58
+ }, 200);
59
+ });
60
+ }
61
+
62
+ return Promise.reject(error);
48
63
  }
49
64
 
50
65
  return typeof modImport === 'function' ? modImport() : Promise.resolve(modImport);