@shopify/hydrogen 0.6.1 → 0.7.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 (147) hide show
  1. package/dist/esnext/components/AddToCartButton/AddToCartButton.client.d.ts +1 -1
  2. package/dist/esnext/components/AddToCartButton/AddToCartButton.client.js +3 -3
  3. package/dist/esnext/components/BuyNowButton/BuyNowButton.client.js +1 -5
  4. package/dist/esnext/components/CartProvider/hooks.js +4 -4
  5. package/dist/esnext/components/Link/Link.client.d.ts +1 -0
  6. package/dist/esnext/components/Link/Link.client.js +1 -0
  7. package/dist/esnext/components/Link/index.d.ts +1 -0
  8. package/dist/esnext/components/Link/index.js +1 -0
  9. package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.d.ts +1 -8
  10. package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.js +1 -3
  11. package/dist/esnext/components/Metafield/Metafield.client.js +8 -0
  12. package/dist/esnext/components/Metafield/MetafieldFragment.d.ts +16 -1
  13. package/dist/esnext/components/Model3D/Model3D.client.d.ts +2 -0
  14. package/dist/esnext/components/Model3D/Model3D.client.js +1 -1
  15. package/dist/esnext/components/ProductProvider/context.d.ts +2 -2
  16. package/dist/esnext/components/ProductProvider/types.d.ts +2 -2
  17. package/dist/esnext/components/SelectedVariantAddToCartButton/SelectedVariantAddToCartButton.client.d.ts +1 -1
  18. package/dist/esnext/components/SelectedVariantAddToCartButton/SelectedVariantAddToCartButton.client.js +1 -1
  19. package/dist/esnext/components/index.d.ts +1 -1
  20. package/dist/esnext/components/index.js +1 -1
  21. package/dist/esnext/entry-server.js +50 -20
  22. package/dist/esnext/foundation/Router/ServerStateRouter.client.js +5 -3
  23. package/dist/esnext/foundation/ServerStateProvider/ServerStateProvider.client.d.ts +19 -18
  24. package/dist/esnext/foundation/ServerStateProvider/ServerStateProvider.client.js +22 -22
  25. package/dist/esnext/foundation/ServerStateProvider/index.d.ts +1 -1
  26. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.d.ts +1 -1
  27. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.js +2 -1
  28. package/dist/esnext/foundation/ShopifyProvider/ShopifyServerProvider.server.js +1 -1
  29. package/dist/esnext/foundation/ShopifyProvider/consts.d.ts +1 -0
  30. package/dist/esnext/foundation/ShopifyProvider/consts.js +1 -0
  31. package/dist/esnext/{hooks → foundation}/useQuery/QueryProvider.d.ts +0 -0
  32. package/dist/esnext/{hooks → foundation}/useQuery/QueryProvider.js +0 -0
  33. package/dist/esnext/{hooks → foundation}/useQuery/hooks.d.ts +0 -0
  34. package/dist/esnext/{hooks → foundation}/useQuery/hooks.js +13 -8
  35. package/dist/esnext/{hooks → foundation}/useQuery/index.d.ts +0 -0
  36. package/dist/esnext/{hooks → foundation}/useQuery/index.js +0 -0
  37. package/dist/esnext/foundation/useServerState/use-server-state.d.ts +1 -1
  38. package/dist/esnext/framework/ClientMarker/ClientMarker.d.ts +2 -3
  39. package/dist/esnext/framework/ClientMarker/ClientMarker.js +24 -1
  40. package/dist/esnext/framework/Hydration/Cache.client.js +5 -2
  41. package/dist/esnext/framework/Hydration/ServerComponentResponse.server.d.ts +10 -0
  42. package/dist/esnext/framework/Hydration/ServerComponentResponse.server.js +13 -0
  43. package/dist/esnext/framework/Hydration/client-imports.d.ts +1 -0
  44. package/dist/esnext/framework/Hydration/client-imports.js +25 -0
  45. package/dist/esnext/framework/Hydration/react-utils.js +1 -0
  46. package/dist/esnext/framework/Hydration/wire.server.js +1 -1
  47. package/dist/esnext/framework/graphiql.js +1 -1
  48. package/dist/esnext/framework/middleware.d.ts +5 -1
  49. package/dist/esnext/framework/middleware.js +12 -8
  50. package/dist/esnext/framework/plugins/resolver.d.ts +1 -0
  51. package/dist/esnext/framework/plugins/resolver.js +3 -0
  52. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +4 -1
  53. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.js +8 -2
  54. package/dist/esnext/framework/plugins/vite-plugin-react-server-components-shim.js +41 -75
  55. package/dist/esnext/framework/server-components.d.ts +2 -10
  56. package/dist/esnext/framework/server-components.js +64 -68
  57. package/dist/esnext/graphql/graphql-constants.d.ts +51 -14
  58. package/dist/esnext/graphql/graphql-constants.js +89 -15
  59. package/dist/esnext/handle-event.js +8 -4
  60. package/dist/esnext/hooks/index.d.ts +1 -1
  61. package/dist/esnext/hooks/index.js +1 -1
  62. package/dist/esnext/hooks/useAvailableCountries/useAvailableCountries.js +1 -1
  63. package/dist/esnext/hooks/useCountry/useCountry.js +1 -1
  64. package/dist/esnext/hooks/useMoney/hooks.js +2 -16
  65. package/dist/esnext/hooks/useParsedMetafields/useParsedMetafields.d.ts +2 -3
  66. package/dist/esnext/hooks/useProductOptions/types.d.ts +2 -3
  67. package/dist/esnext/hooks/useShopQuery/hooks.js +1 -1
  68. package/dist/esnext/types.d.ts +19 -3
  69. package/dist/esnext/utilities/flattenConnection/flattenConnection.d.ts +0 -7
  70. package/dist/esnext/utilities/flattenConnection/flattenConnection.js +0 -7
  71. package/dist/esnext/utilities/isClient/isClient.d.ts +0 -4
  72. package/dist/esnext/utilities/isClient/isClient.js +0 -4
  73. package/dist/esnext/utilities/isServer/isServer.d.ts +0 -4
  74. package/dist/esnext/utilities/isServer/isServer.js +0 -4
  75. package/dist/esnext/utilities/parseMetafieldValue/parseMetafieldValue.d.ts +2 -33
  76. package/dist/esnext/utilities/parseMetafieldValue/parseMetafieldValue.js +0 -31
  77. package/dist/esnext/version.d.ts +1 -1
  78. package/dist/esnext/version.js +1 -1
  79. package/dist/node/foundation/ShopifyProvider/consts.d.ts +1 -0
  80. package/dist/node/foundation/ShopifyProvider/consts.js +4 -0
  81. package/dist/node/framework/ClientMarker/ClientMarker.d.ts +2 -3
  82. package/dist/node/framework/ClientMarker/ClientMarker.js +26 -3
  83. package/dist/node/framework/Hydration/Cache.client.js +7 -20
  84. package/dist/node/framework/Hydration/ServerComponentResponse.server.d.ts +10 -0
  85. package/dist/node/framework/Hydration/ServerComponentResponse.server.js +13 -0
  86. package/dist/node/framework/Hydration/client-imports.d.ts +1 -0
  87. package/dist/node/framework/Hydration/client-imports.js +28 -0
  88. package/dist/node/framework/Hydration/react-utils.js +1 -0
  89. package/dist/node/framework/Hydration/wire.server.js +1 -1
  90. package/dist/node/framework/graphiql.js +2 -2
  91. package/dist/node/framework/middleware.d.ts +5 -1
  92. package/dist/node/framework/middleware.js +15 -9
  93. package/dist/node/framework/plugins/resolver.d.ts +1 -0
  94. package/dist/node/framework/plugins/resolver.js +7 -0
  95. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +4 -1
  96. package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.js +8 -2
  97. package/dist/node/framework/plugins/vite-plugin-react-server-components-shim.js +41 -75
  98. package/dist/node/framework/server-components.d.ts +2 -10
  99. package/dist/node/framework/server-components.js +69 -71
  100. package/dist/node/handle-event.js +8 -4
  101. package/dist/node/types.d.ts +19 -3
  102. package/dist/node/utilities/flattenConnection/flattenConnection.d.ts +0 -7
  103. package/dist/node/utilities/flattenConnection/flattenConnection.js +0 -7
  104. package/dist/node/utilities/isClient/isClient.d.ts +0 -4
  105. package/dist/node/utilities/isClient/isClient.js +0 -4
  106. package/dist/node/utilities/isServer/isServer.d.ts +0 -4
  107. package/dist/node/utilities/isServer/isServer.js +0 -4
  108. package/dist/node/utilities/parseMetafieldValue/parseMetafieldValue.d.ts +2 -33
  109. package/dist/node/utilities/parseMetafieldValue/parseMetafieldValue.js +0 -31
  110. package/dist/node/version.d.ts +1 -1
  111. package/dist/node/version.js +1 -1
  112. package/dist/worker/framework/Hydration/ServerComponentResponse.server.d.ts +10 -0
  113. package/dist/worker/framework/Hydration/ServerComponentResponse.server.js +13 -0
  114. package/dist/worker/handle-event.js +8 -4
  115. package/dist/worker/types.d.ts +19 -3
  116. package/marker.js +1 -1
  117. package/package.json +11 -7
  118. package/dist/node/foundation/Router/DefaultRoutes.d.ts +0 -20
  119. package/dist/node/foundation/Router/DefaultRoutes.js +0 -78
  120. package/dist/node/foundation/Router/index.d.ts +0 -1
  121. package/dist/node/foundation/Router/index.js +0 -5
  122. package/dist/node/foundation/ServerStateProvider/ServerStateProvider.client.d.ts +0 -24
  123. package/dist/node/foundation/ServerStateProvider/ServerStateProvider.client.js +0 -66
  124. package/dist/node/foundation/ServerStateProvider/index.d.ts +0 -1
  125. package/dist/node/foundation/ServerStateProvider/index.js +0 -6
  126. package/dist/node/foundation/ShopifyProvider/ShopifyContext.d.ts +0 -1
  127. package/dist/node/foundation/ShopifyProvider/ShopifyContext.js +0 -5
  128. package/dist/node/foundation/ShopifyProvider/ShopifyProvider.d.ts +0 -7
  129. package/dist/node/foundation/ShopifyProvider/ShopifyProvider.js +0 -42
  130. package/dist/node/foundation/ShopifyProvider/ShopifyServerProvider.server.d.ts +0 -8
  131. package/dist/node/foundation/ShopifyProvider/ShopifyServerProvider.server.js +0 -14
  132. package/dist/node/foundation/ShopifyProvider/index.d.ts +0 -1
  133. package/dist/node/foundation/ShopifyProvider/index.js +0 -6
  134. package/dist/node/foundation/ShopifyProvider/types.d.ts +0 -14
  135. package/dist/node/foundation/ShopifyProvider/types.js +0 -2
  136. package/dist/node/foundation/index.d.ts +0 -5
  137. package/dist/node/foundation/index.js +0 -23
  138. package/dist/node/foundation/useShop/index.d.ts +0 -1
  139. package/dist/node/foundation/useShop/index.js +0 -5
  140. package/dist/node/foundation/useShop/use-shop.d.ts +0 -5
  141. package/dist/node/foundation/useShop/use-shop.js +0 -16
  142. package/dist/node/hooks/useQuery/QueryProvider.d.ts +0 -6
  143. package/dist/node/hooks/useQuery/QueryProvider.js +0 -20
  144. package/dist/node/hooks/useQuery/hooks.d.ts +0 -15
  145. package/dist/node/hooks/useQuery/hooks.js +0 -67
  146. package/dist/node/hooks/useQuery/index.d.ts +0 -2
  147. package/dist/node/hooks/useQuery/index.js +0 -7
@@ -1,7 +1,30 @@
1
1
  import React, { useContext } from 'react';
2
2
  import { HydrationContext } from '../Hydration/HydrationContext.server';
3
3
  import { renderReactProps } from '../Hydration/react-utils';
4
- export function ClientMarker({ name, id, props: allProps, component: Component, named, }) {
4
+ export function wrapInClientMarker(meta) {
5
+ const { component, name } = meta;
6
+ if (!component ||
7
+ (typeof component !== 'function' &&
8
+ !Object.prototype.hasOwnProperty.call(component, 'render'))) {
9
+ // This is not a React component, return it as is.
10
+ return component;
11
+ }
12
+ // Use object syntax here to make sure the function name
13
+ // comes from the meta params for better error stacks.
14
+ const wrappedComponent = {
15
+ // eslint-disable-next-line react/display-name
16
+ [name]: (props) => React.createElement(ClientMarker, { ...{ props, meta } }),
17
+ }[name];
18
+ // Relay component properties such as `Image.Fragment`
19
+ for (const [key, value] of Object.entries(component)) {
20
+ Object.defineProperty(wrappedComponent, key, {
21
+ enumerable: true,
22
+ value,
23
+ });
24
+ }
25
+ return wrappedComponent;
26
+ }
27
+ function ClientMarker({ props: allProps, meta: { name, id, component: Component, named }, }) {
5
28
  const isHydrating = useContext(HydrationContext);
6
29
  if (!isHydrating)
7
30
  return React.createElement(Component, { ...allProps });
@@ -1,5 +1,6 @@
1
1
  import { createElement, Fragment } from 'react';
2
2
  import { wrapPromise } from '../../utilities';
3
+ import importClientComponent from './client-imports';
3
4
  const cache = new Map();
4
5
  const moduleCache = new Map();
5
6
  /**
@@ -110,7 +111,9 @@ export async function convertHydrationResponseToReactComponents(response) {
110
111
  function createManifestFromWirePayload(payload) {
111
112
  return payload.split('\n').reduce((memo, row) => {
112
113
  const [key, ...values] = row.split(':');
113
- memo[key] = JSON.parse(values.join(':'));
114
+ if (key) {
115
+ memo[key] = JSON.parse(values.join(':'));
116
+ }
114
117
  return memo;
115
118
  }, {});
116
119
  }
@@ -122,7 +125,7 @@ async function eagerLoadModules(manifest) {
122
125
  if (moduleCache.has(module.id)) {
123
126
  return moduleCache.get(module.id);
124
127
  }
125
- const mod = await import(/* @vite-ignore */ module.id);
128
+ const mod = await importClientComponent(module.id);
126
129
  moduleCache.set(module.id, mod);
127
130
  return mod;
128
131
  })
@@ -2,6 +2,10 @@ import { CacheOptions } from '../../types';
2
2
  export declare class ServerComponentResponse extends Response {
3
3
  private wait;
4
4
  private cacheOptions?;
5
+ customStatus?: {
6
+ code?: number;
7
+ text?: string;
8
+ };
5
9
  /**
6
10
  * Allow custom body to be a string or a Promise.
7
11
  */
@@ -14,6 +18,12 @@ export declare class ServerComponentResponse extends Response {
14
18
  canStream(): boolean;
15
19
  cache(options: CacheOptions): void;
16
20
  get cacheControlHeader(): string;
21
+ writeHead({ status, statusText, headers, }?: {
22
+ status?: number;
23
+ statusText?: string;
24
+ headers?: Record<string, any>;
25
+ }): void;
26
+ redirect(location: string, status?: number): void;
17
27
  /**
18
28
  * Send the response from a Server Component. Renders React components to string,
19
29
  * and returns `null` to make React happy.
@@ -30,6 +30,19 @@ export class ServerComponentResponse extends Response {
30
30
  };
31
31
  return generateCacheControlHeader(options);
32
32
  }
33
+ writeHead({ status, statusText, headers, } = {}) {
34
+ if (status || statusText) {
35
+ this.customStatus = { code: status, text: statusText };
36
+ }
37
+ if (headers) {
38
+ for (const [key, value] of Object.entries(headers)) {
39
+ this.headers.set(key, value);
40
+ }
41
+ }
42
+ }
43
+ redirect(location, status = 307) {
44
+ this.writeHead({ status, headers: { location } });
45
+ }
33
46
  /**
34
47
  * Send the response from a Server Component. Renders React components to string,
35
48
  * and returns `null` to make React happy.
@@ -0,0 +1 @@
1
+ export default function importClientComponent(moduleId: string): any;
@@ -0,0 +1,25 @@
1
+ // Transform relative paths to absolute in order
2
+ // to match component IDs from ClientMarker.
3
+ function normalizeComponentPaths(componentObject, prefix) {
4
+ return Object.entries(componentObject).reduce((acc, [key, value]) => {
5
+ acc[prefix + key.replace(/\.\.\//gm, '')] = value;
6
+ return acc;
7
+ }, {});
8
+ }
9
+ // These strings are replaced in a plugin with real globs
10
+ // and paths that depend on the user project structure.
11
+ const allClientComponents = {
12
+ ...normalizeComponentPaths(
13
+ // @ts-ignore
14
+ import.meta.glob('__LIB_COMPONENTS_GLOB__'), `__LIB_COMPONENTS_PREFIX__`),
15
+ ...normalizeComponentPaths(
16
+ // @ts-ignore
17
+ import.meta.glob('__USER_COMPONENTS_GLOB__'), `__USER_COMPONENTS_PREFIX__`),
18
+ };
19
+ export default function importClientComponent(moduleId) {
20
+ const modImport = allClientComponents[moduleId];
21
+ if (!modImport) {
22
+ throw new Error(`Could not find client component ${moduleId}`);
23
+ }
24
+ return modImport();
25
+ }
@@ -26,6 +26,7 @@ export function renderReactProps(props) {
26
26
  */
27
27
  function renderReactProp(prop) {
28
28
  if (typeof prop === 'object' &&
29
+ prop !== null &&
29
30
  prop['$$typeof'] === Symbol.for('react.element')) {
30
31
  if (prop.type instanceof Function) {
31
32
  /**
@@ -68,7 +68,7 @@ export function generateWireSyntaxFromRenderedHtml(html) {
68
68
  .map((component, idx) => {
69
69
  return `M${idx + 1}:${JSON.stringify(component)}`;
70
70
  })
71
- .join('\n') + `\nJ0:${JSON.stringify(wireModel)}`);
71
+ .join('\n') + `\nJ0:${JSON.stringify(wireModel)}`).trim();
72
72
  }
73
73
  function isDomNode(item) {
74
74
  return item !== null && typeof item === 'object' && '_owner' in item;
@@ -1,4 +1,4 @@
1
- import { DEFAULT_API_VERSION } from '../foundation';
1
+ import { DEFAULT_API_VERSION } from '../foundation/ShopifyProvider/consts';
2
2
  export function graphiqlHtml(shop, token, apiVersion = DEFAULT_API_VERSION) {
3
3
  return `<html>
4
4
  <head>
@@ -10,9 +10,13 @@ declare type HydrogenMiddlewareArgs = {
10
10
  devServer?: ViteDevServer;
11
11
  cache?: Cache;
12
12
  };
13
+ export declare function graphiqlMiddleware({ shopifyConfig, dev, }: {
14
+ shopifyConfig: ShopifyConfig;
15
+ dev: boolean;
16
+ }): (request: IncomingMessage, response: http.ServerResponse, next: NextFunction) => Promise<void>;
13
17
  /**
14
18
  * Provides middleware to Node.js Express-like servers. Used by the Hydrogen
15
19
  * Vite dev server plugin as well as production Node.js implementation.
16
20
  */
17
- export default function hydrogenMiddleware({ dev, shopifyConfig, cache, indexTemplate, getServerEntrypoint, devServer, }: HydrogenMiddlewareArgs): (request: IncomingMessage, response: http.ServerResponse, next: NextFunction) => Promise<void>;
21
+ export declare function hydrogenMiddleware({ dev, cache, indexTemplate, getServerEntrypoint, devServer, }: HydrogenMiddlewareArgs): (request: IncomingMessage, response: http.ServerResponse, next: NextFunction) => Promise<void>;
18
22
  export {};
@@ -1,15 +1,20 @@
1
1
  import { graphiqlHtml } from './graphiql';
2
2
  import handleEvent from '../handle-event';
3
- /**
4
- * Provides middleware to Node.js Express-like servers. Used by the Hydrogen
5
- * Vite dev server plugin as well as production Node.js implementation.
6
- */
7
- export default function hydrogenMiddleware({ dev, shopifyConfig, cache, indexTemplate, getServerEntrypoint, devServer, }) {
3
+ export function graphiqlMiddleware({ shopifyConfig, dev, }) {
8
4
  return async function (request, response, next) {
9
5
  const graphiqlRequest = dev && isGraphiqlRequest(request);
10
6
  if (graphiqlRequest) {
11
7
  return respondWithGraphiql(response, shopifyConfig);
12
8
  }
9
+ next();
10
+ };
11
+ }
12
+ /**
13
+ * Provides middleware to Node.js Express-like servers. Used by the Hydrogen
14
+ * Vite dev server plugin as well as production Node.js implementation.
15
+ */
16
+ export function hydrogenMiddleware({ dev, cache, indexTemplate, getServerEntrypoint, devServer, }) {
17
+ return async function (request, response, next) {
13
18
  const url = new URL('http://' + request.headers.host + request.originalUrl);
14
19
  const isReactHydrationRequest = url.pathname === '/react';
15
20
  /**
@@ -96,9 +101,8 @@ export default function hydrogenMiddleware({ dev, shopifyConfig, cache, indexTem
96
101
  }
97
102
  function shouldInterceptRequest(request, isReactHydrationRequest) {
98
103
  var _a;
99
- return ((/text\/html|application\/hydrogen/.test((_a = request.headers['accept']) !== null && _a !== void 0 ? _a : '') ||
100
- isReactHydrationRequest) &&
101
- request.url !== '/favicon.ico');
104
+ return (/text\/html|application\/hydrogen/.test((_a = request.headers['accept']) !== null && _a !== void 0 ? _a : '') ||
105
+ isReactHydrationRequest);
102
106
  }
103
107
  /**
104
108
  * /graphiql and /___graphql are supported
@@ -0,0 +1 @@
1
+ export declare function resolve(path: string): string;
@@ -0,0 +1,3 @@
1
+ export function resolve(path) {
2
+ return require.resolve(path);
3
+ }
@@ -1,7 +1,7 @@
1
1
  export default () => {
2
2
  return {
3
3
  name: 'vite-plugin-hydrogen-config',
4
- config: () => ({
4
+ config: (_, env) => ({
5
5
  resolve: {
6
6
  alias: {
7
7
  /**
@@ -52,6 +52,9 @@ export default () => {
52
52
  'react-router-dom',
53
53
  ],
54
54
  },
55
+ define: {
56
+ __DEV__: env.mode !== 'production',
57
+ },
55
58
  }),
56
59
  };
57
60
  };
@@ -1,6 +1,6 @@
1
1
  import path from 'path';
2
2
  import { promises as fs } from 'fs';
3
- import hydrogenMiddleware from '../middleware';
3
+ import { hydrogenMiddleware, graphiqlMiddleware } from '../middleware';
4
4
  import { InMemoryCache } from '../cache/in-memory';
5
5
  export default (shopifyConfig, pluginOptions) => {
6
6
  return {
@@ -17,7 +17,13 @@ export default (shopifyConfig, pluginOptions) => {
17
17
  const indexHtml = await fs.readFile(resolve('index.html'), 'utf-8');
18
18
  return await server.transformIndexHtml(url, indexHtml);
19
19
  }
20
- server.middlewares.use(hydrogenMiddleware({
20
+ // The default vite middleware rewrites the URL `/graphqil` to `/index.html`
21
+ // By running this middleware first, we avoid that.
22
+ server.middlewares.use(graphiqlMiddleware({
23
+ shopifyConfig,
24
+ dev: true,
25
+ }));
26
+ return () => server.middlewares.use(hydrogenMiddleware({
21
27
  dev: true,
22
28
  shopifyConfig,
23
29
  indexTemplate: getIndexTemplate,
@@ -1,37 +1,16 @@
1
+ import { normalizePath } from 'vite';
1
2
  import path from 'path';
3
+ import { proxyClientComponent } from '../server-components';
4
+ import { resolve } from './resolver';
2
5
  import { promises as fs } from 'fs';
3
- import glob from 'fast-glob';
4
- import { tagClientComponents, wrapClientComponents } from '../server-components';
5
6
  export default () => {
6
7
  let config;
7
- let clientManifest;
8
8
  return {
9
9
  name: 'vite-plugin-react-server-components-shim',
10
10
  enforce: 'pre',
11
11
  configResolved(_config) {
12
12
  config = _config;
13
13
  },
14
- buildStart() {
15
- if (config.build.ssr || config.command !== 'build')
16
- return;
17
- const hydrogenComponentPath = path.dirname(
18
- // eslint-disable-next-line node/no-missing-require
19
- require.resolve('@shopify/hydrogen'));
20
- /**
21
- * Grab each of the client components in this project and emit them as chunks.
22
- * This allows us to dynamically import them later during partial hydration in production.
23
- */
24
- const clientComponents = glob
25
- .sync(path.resolve(config.root, './src/**/*.client.(j|t)sx'))
26
- .concat(glob.sync(path.join(hydrogenComponentPath, '**/*.client.js')));
27
- clientComponents.forEach((id) => {
28
- this.emitFile({
29
- type: 'chunk',
30
- id,
31
- preserveSignature: 'strict',
32
- });
33
- });
34
- },
35
14
  async resolveId(source, importer) {
36
15
  if (!importer)
37
16
  return null;
@@ -56,39 +35,51 @@ export default () => {
56
35
  'When using Hydrogen components within Client Components, use the `@shopify/hydrogen/client` entrypoint instead.');
57
36
  }
58
37
  },
59
- transform(src, id, options) {
38
+ async load(id, options) {
60
39
  if (!isSSR(options))
61
40
  return null;
62
- /**
63
- * When a server component imports a client component, tag a `?fromServer`
64
- * identifier at the end of the import to indicate that we should transform
65
- * it with a ClientMarker (below).
66
- *
67
- * We are manually passing `@shopify/hydrogen/client` as an additional "from"
68
- * identifier to allow local Server Components to import them as tagged Client Components.
69
- * We should also accept this as a plugin argument for other third-party packages.
70
- */
71
- if (/\.server\.(j|t)sx?$/.test(id)) {
72
- return tagClientComponents(src);
41
+ // Wrapped components won't match this becase they end in ?no-proxy
42
+ if (/\.client\.[jt]sx?$/.test(id)) {
43
+ return proxyClientComponent({ id });
44
+ }
45
+ // Temporary fix for sourcemap warnings in client components. This can be fixed in @vitejs/react-plugin.
46
+ // `react-ssr-prepass` sourcemap seems to be broken and crashes in workers/Jest.
47
+ if (id.endsWith('?no-proxy') ||
48
+ id.includes('dist/react-ssr-prepass.es.js')) {
49
+ return {
50
+ code: await fs.readFile(id.split('?')[0], 'utf-8'),
51
+ map: { mappings: '' },
52
+ };
73
53
  }
54
+ return null;
74
55
  },
75
- async load(id, options) {
76
- if (!isSSR(options))
77
- return null;
56
+ transform(code, id) {
78
57
  /**
79
- * Client components being loaded from server components need to be
80
- * wrapped in a ClientMarker so we can serialize their props and
81
- * dynamically load them in the browser.
58
+ * In order to allow dynamic component imports from RSC, we use Vite's import.meta.glob.
59
+ * This replaces the glob import path placeholders in importer-dev.ts with resolved paths
60
+ * to all client components (both user and Hydrogen components).
61
+ *
62
+ * NOTE: Glob import paths MUST be relative to the importer file (client-imports.ts) in
63
+ * order to get the `?v=xxx` querystring from Vite added to the import URL.
64
+ * If the paths are relative to the root instead, Vite won't add the querystring
65
+ * and we will have duplicated files in the browser (with duplicated contexts, etc).
82
66
  */
83
- if (id.includes('?fromServer')) {
84
- return await wrapClientComponents({
85
- id,
86
- getManifestFile: getFileFromClientManifest,
87
- root: config.root,
88
- isBuild: config.command === 'build',
89
- });
67
+ if (id.includes('/Hydration/client-imports')) {
68
+ // eslint-disable-next-line node/no-missing-require
69
+ const hydrogenPath = path.dirname(resolve('@shopify/hydrogen'));
70
+ const importerPath = path.join(hydrogenPath, 'framework', 'Hydration');
71
+ const importerToRootPath = normalizePath(path.relative(importerPath, config.root));
72
+ const [importerToRootNested] = importerToRootPath.match(/(\.\.\/)+(\.\.)?/) || [];
73
+ const userPrefix = path.normalize(path.join(importerPath, importerToRootNested.replace(/\/?$/, path.sep)));
74
+ const userGlob = path.join(importerToRootPath, 'src', '**/*.client.[jt]sx');
75
+ const libPrefix = hydrogenPath + path.sep;
76
+ const libGlob = path.join(path.relative(importerPath, hydrogenPath), 'components', '**/*.client.js');
77
+ return code
78
+ .replace('__USER_COMPONENTS_PREFIX__', normalizePath(userPrefix))
79
+ .replace('__USER_COMPONENTS_GLOB__', normalizePath(userGlob))
80
+ .replace('__LIB_COMPONENTS_PREFIX__', normalizePath(libPrefix))
81
+ .replace('__LIB_COMPONENTS_GLOB__', normalizePath(libGlob));
90
82
  }
91
- return null;
92
83
  },
93
84
  };
94
85
  // Mitigation for upcoming minor Vite update
@@ -106,29 +97,4 @@ export default () => {
106
97
  }
107
98
  return false;
108
99
  }
109
- async function getFileFromClientManifest(manifestId) {
110
- const manifest = await getClientManifest();
111
- const fileName = '/' + manifestId.split('/').pop();
112
- const matchingKey = Object.keys(manifest).find((key) => key.endsWith(fileName));
113
- if (!matchingKey) {
114
- throw new Error(`Could not find a matching entry in the manifest for: ${manifestId}`);
115
- }
116
- return manifest[matchingKey].file;
117
- }
118
- async function getClientManifest() {
119
- if (config.command !== 'build') {
120
- return {};
121
- }
122
- if (clientManifest)
123
- return clientManifest;
124
- try {
125
- const manifest = JSON.parse(await fs.readFile(path.resolve(config.root, './dist/client/manifest.json'), 'utf-8'));
126
- clientManifest = manifest;
127
- return manifest;
128
- }
129
- catch (e) {
130
- console.error(`Failed to load client manifest:`);
131
- console.error(e);
132
- }
133
- }
134
100
  };
@@ -1,12 +1,4 @@
1
- export declare function wrapClientComponents({ id, getManifestFile, root, isBuild, }: {
1
+ export declare function proxyClientComponent({ id, src, }: {
2
2
  id: string;
3
- getManifestFile: (manifestId: string) => Promise<string>;
4
- root: string;
5
- isBuild: boolean;
3
+ src?: string;
6
4
  }): Promise<string>;
7
- export declare function tagClientComponents(src: string, additionalReferences?: Array<string | RegExp>): {
8
- code: string;
9
- map: {
10
- mappings: string;
11
- };
12
- };
@@ -1,72 +1,68 @@
1
- export async function wrapClientComponents({ id, getManifestFile, root, isBuild, }) {
2
- const normalizedId = id.slice(0, id.indexOf('?fromServer'));
3
- const manifestId = normalizedId.replace(root, '');
4
- const name = id.split('/').pop().split('.').shift();
5
- /**
6
- * ?fromServer can designate named exports as a comma-separated list.
7
- * ?fromServer=Foo,Bar
8
- *
9
- * Re-named exports are also supported:
10
- * ?fromServer=Foo:Baz,Bar
11
- */
12
- const names = id.includes('?fromServer=')
13
- ? id
14
- .slice(id.indexOf('?fromServer=') + '?fromServer='.length)
15
- .split(',')
16
- .map((name) => name.split(':').shift())
17
- : [];
18
- /**
19
- * Determine the id of the chunk to be imported. If we're building
20
- * the production bundle, we need to reference the chunk generated
21
- * during the client manifest. Otherwise, we can pass the normalizedId
22
- * and Vite's dev server will load it as expected.
23
- */
24
- const importId = isBuild
25
- ? '/' + (await getManifestFile(manifestId))
26
- : normalizedId;
27
- const isNamedExport = names.length > 0;
28
- let code = `import React from 'react';
29
- import {ClientMarker} from '@shopify/hydrogen/marker';`;
30
- if (!isNamedExport) {
31
- code += `
32
- import _Component from '${normalizedId}';
33
-
34
- export default function _ClientComponent(props) {
35
- return React.createElement(ClientMarker, { name: '${name}', id: '${importId}', props, component: _Component, named: false });
36
- }
37
- export * from '${normalizedId}';`;
1
+ import { init, parse } from 'es-module-lexer';
2
+ import { promises as fs } from 'fs';
3
+ import { transformWithEsbuild } from 'vite';
4
+ import MagicString from 'magic-string';
5
+ export async function proxyClientComponent({ id, src, }) {
6
+ var _a;
7
+ const defaultComponentName = (_a = id.split('/').pop()) === null || _a === void 0 ? void 0 : _a.split('.').shift();
8
+ // Modify the import ID to avoid infinite wraps
9
+ const importFrom = `${id}?no-proxy`;
10
+ await init;
11
+ if (!src) {
12
+ src = await fs.readFile(id, 'utf-8');
38
13
  }
39
- else {
40
- code += `
41
- import {${names
42
- .map((name, idx) => name + ' as ' + `_Component${idx}`)
43
- .join(', ')}} from '${normalizedId}';`;
44
- names.forEach((name, idx) => {
45
- code += `\n
46
- export function ${name}(props) {
47
- return React.createElement(ClientMarker, { name: '${name}', id: '${importId}', props, component: _Component${idx}, named: true });
48
- }`;
49
- });
14
+ const { code } = await transformWithEsbuild(src, id);
15
+ const [, exportStatements] = parse(code);
16
+ const hasDefaultExport = exportStatements.includes('default');
17
+ // Split namedImports in components to wrap and everything else (e.g. GQL Fragments)
18
+ const namedImports = exportStatements.reduce((acc, i) => {
19
+ if (i !== 'default') {
20
+ // Add here any other naming pattern for a non-component export
21
+ if (/^use[A-Z]|Fragment$|Context$|^[A-Z_]+$/.test(i)) {
22
+ acc.other.push(i);
23
+ }
24
+ else {
25
+ acc.components.push(i);
26
+ }
27
+ }
28
+ return acc;
29
+ }, { components: [], other: [] });
30
+ if (!hasDefaultExport && namedImports.components.length === 0) {
31
+ return `export * from '${importFrom}';\n`;
50
32
  }
51
- return code;
33
+ const s = new MagicString(`import {wrapInClientMarker} from '@shopify/hydrogen/marker';`);
34
+ s.append('\nimport ');
35
+ if (hasDefaultExport) {
36
+ s.append(defaultComponentName);
37
+ if (namedImports.components.length > 0) {
38
+ s.append(', ');
39
+ }
40
+ }
41
+ if (namedImports.components.length) {
42
+ s.append('* as namedImports');
43
+ }
44
+ s.append(` from '${importFrom}';\n\n`);
45
+ // Re-export other stuff directly without wrapping
46
+ if (namedImports.other.length > 0) {
47
+ s.append(`export {${namedImports.other.join(', ')}} from '${importFrom}';\n`);
48
+ }
49
+ if (hasDefaultExport) {
50
+ s.append(generateComponentExport({
51
+ id,
52
+ componentName: defaultComponentName,
53
+ isDefault: true,
54
+ }));
55
+ }
56
+ namedImports.components.forEach((name) => s.append(generateComponentExport({
57
+ id,
58
+ componentName: name,
59
+ isDefault: false,
60
+ })));
61
+ return s.toString();
52
62
  }
53
- export function tagClientComponents(src, additionalReferences = []) {
54
- const modulePatterns = [/[\w\/\.]+\.client(?:\.(?:j|t)sx?)?/]
55
- // @ts-ignore
56
- .concat(additionalReferences)
57
- .map((pattern) => (pattern instanceof RegExp ? pattern.source : pattern));
58
- const fromModulePattern = modulePatterns.join('|');
59
- /**
60
- * Default exports
61
- * @see https://rubular.com/r/XZjsrolet5twvB
62
- */
63
- let regex = new RegExp(`import\\s*\\w*\\s*from\\s*(?:'|")(?:(${fromModulePattern}))`, 'g');
64
- let code = src.replace(regex, (mod) => mod + '?fromServer');
65
- /**
66
- * Named exports
67
- * @see https://rubular.com/r/6qdREcs4T9Nw1e
68
- */
69
- regex = new RegExp(`import\\s*{([\\w\\s,]+)}\\s*from\\s*(?:'|")(?:(${fromModulePattern}))`, 'g');
70
- code = code.replace(regex, (mod, imports) => `${mod}?fromServer=${imports.replace(/ as /g, ':').replace(/ /g, '')}`);
71
- return { code, map: { mappings: '' } };
63
+ function generateComponentExport({ id, isDefault, componentName, }) {
64
+ const component = isDefault
65
+ ? componentName
66
+ : `namedImports['${componentName}']`;
67
+ return `export ${isDefault ? 'default' : `const ${componentName} =`} wrapInClientMarker({ name: '${componentName}', id: '${id}', component: ${component}, named: ${!isDefault} });\n`;
72
68
  }