@shopify/cli-hydrogen 3.27.0 → 4.0.0-alpha.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 (249) hide show
  1. package/dist/commands/hydrogen/build.js +89 -0
  2. package/dist/commands/hydrogen/dev.js +116 -0
  3. package/dist/commands/hydrogen/init.js +42 -0
  4. package/dist/commands/hydrogen/preview.js +34 -0
  5. package/dist/hooks/init.js +21 -0
  6. package/dist/templates/demo-store/.editorconfig +8 -0
  7. package/dist/templates/demo-store/.eslintignore +4 -0
  8. package/dist/templates/demo-store/.eslintrc.js +16 -0
  9. package/dist/templates/demo-store/.graphqlrc.yml +1 -0
  10. package/dist/templates/demo-store/.prettierignore +2 -0
  11. package/dist/templates/demo-store/.turbo/turbo-build.log +13 -0
  12. package/dist/templates/demo-store/app/components/AccountAddressBook.tsx +97 -0
  13. package/dist/templates/demo-store/app/components/AccountDetails.tsx +41 -0
  14. package/dist/templates/demo-store/app/components/AddToCartButton.tsx +42 -0
  15. package/dist/templates/demo-store/app/components/Breadcrumbs.tsx +36 -0
  16. package/dist/templates/demo-store/app/components/Button.tsx +56 -0
  17. package/dist/templates/demo-store/app/components/Cart.tsx +431 -0
  18. package/dist/templates/demo-store/app/components/CartLoading.tsx +50 -0
  19. package/dist/templates/demo-store/app/components/CountrySelector.tsx +180 -0
  20. package/dist/templates/demo-store/app/components/Drawer.tsx +115 -0
  21. package/dist/templates/demo-store/app/components/FeaturedCollections.tsx +54 -0
  22. package/dist/templates/demo-store/app/components/FeaturedProducts.tsx +116 -0
  23. package/dist/templates/demo-store/app/components/FeaturedSection.tsx +39 -0
  24. package/dist/templates/demo-store/app/components/GenericError.tsx +58 -0
  25. package/dist/templates/demo-store/app/components/Grid.tsx +44 -0
  26. package/dist/templates/demo-store/app/components/Hero.tsx +136 -0
  27. package/dist/templates/demo-store/app/components/Icon.tsx +253 -0
  28. package/dist/templates/demo-store/app/components/Input.tsx +24 -0
  29. package/dist/templates/demo-store/app/components/Layout.tsx +492 -0
  30. package/dist/templates/demo-store/app/components/Link.tsx +46 -0
  31. package/dist/templates/demo-store/app/components/Modal.tsx +46 -0
  32. package/dist/templates/demo-store/app/components/NotFound.tsx +22 -0
  33. package/dist/templates/demo-store/app/components/OrderCard.tsx +85 -0
  34. package/dist/templates/demo-store/app/components/Pagination.tsx +277 -0
  35. package/dist/templates/demo-store/app/components/ProductCard.tsx +146 -0
  36. package/dist/templates/demo-store/app/components/ProductGallery.tsx +114 -0
  37. package/dist/templates/demo-store/app/components/ProductGrid.tsx +93 -0
  38. package/dist/templates/demo-store/app/components/ProductSwimlane.tsx +30 -0
  39. package/dist/templates/demo-store/app/components/Skeleton.tsx +24 -0
  40. package/dist/templates/demo-store/app/components/SortFilter.tsx +411 -0
  41. package/dist/templates/demo-store/app/components/Text.tsx +192 -0
  42. package/dist/templates/demo-store/app/components/index.ts +28 -0
  43. package/dist/templates/demo-store/app/data/countries.ts +194 -0
  44. package/dist/templates/demo-store/app/data/index.ts +1037 -0
  45. package/dist/templates/demo-store/app/entry.client.tsx +4 -0
  46. package/dist/templates/demo-store/app/entry.server.tsx +26 -0
  47. package/dist/templates/demo-store/app/hooks/useCartFetchers.tsx +14 -0
  48. package/dist/templates/demo-store/app/hooks/useIsHydrated.tsx +12 -0
  49. package/dist/templates/demo-store/app/lib/const.ts +10 -0
  50. package/dist/templates/demo-store/app/lib/placeholders.ts +242 -0
  51. package/dist/templates/demo-store/app/lib/seo/common.tsx +324 -0
  52. package/dist/templates/demo-store/app/lib/seo/debugger.tsx +175 -0
  53. package/dist/templates/demo-store/app/lib/seo/image.tsx +32 -0
  54. package/dist/templates/demo-store/app/lib/seo/index.ts +4 -0
  55. package/dist/templates/demo-store/app/lib/seo/seo.tsx +24 -0
  56. package/dist/templates/demo-store/app/lib/seo/types.ts +70 -0
  57. package/dist/templates/demo-store/app/lib/session.server.ts +57 -0
  58. package/dist/templates/demo-store/app/lib/type.ts +21 -0
  59. package/dist/templates/demo-store/app/lib/utils.ts +310 -0
  60. package/dist/templates/demo-store/app/root.tsx +282 -0
  61. package/dist/templates/demo-store/app/routes/$.tsx +7 -0
  62. package/dist/templates/demo-store/app/routes/$lang/$.tsx +1 -0
  63. package/dist/templates/demo-store/app/routes/$lang/[robots.txt].tsx +1 -0
  64. package/dist/templates/demo-store/app/routes/$lang/[sitemap.xml].tsx +1 -0
  65. package/dist/templates/demo-store/app/routes/$lang/account/__private/address/$id.tsx +1 -0
  66. package/dist/templates/demo-store/app/routes/$lang/account/__private/edit.tsx +1 -0
  67. package/dist/templates/demo-store/app/routes/$lang/account/__private/logout.ts +1 -0
  68. package/dist/templates/demo-store/app/routes/$lang/account/__private/orders.$id.tsx +1 -0
  69. package/dist/templates/demo-store/app/routes/$lang/account/__public/activate.$id.$activationToken.tsx +6 -0
  70. package/dist/templates/demo-store/app/routes/$lang/account/__public/login.tsx +7 -0
  71. package/dist/templates/demo-store/app/routes/$lang/account/__public/recover.tsx +1 -0
  72. package/dist/templates/demo-store/app/routes/$lang/account/__public/register.tsx +6 -0
  73. package/dist/templates/demo-store/app/routes/$lang/account/__public/reset.$id.$resetToken.tsx +5 -0
  74. package/dist/templates/demo-store/app/routes/$lang/account.tsx +1 -0
  75. package/dist/templates/demo-store/app/routes/$lang/api/countries.tsx +1 -0
  76. package/dist/templates/demo-store/app/routes/$lang/api/products.tsx +1 -0
  77. package/dist/templates/demo-store/app/routes/$lang/cart.tsx +1 -0
  78. package/dist/templates/demo-store/app/routes/$lang/collections/$collectionHandle.tsx +6 -0
  79. package/dist/templates/demo-store/app/routes/$lang/collections/all.tsx +1 -0
  80. package/dist/templates/demo-store/app/routes/$lang/collections/index.tsx +1 -0
  81. package/dist/templates/demo-store/app/routes/$lang/featured-products.tsx +1 -0
  82. package/dist/templates/demo-store/app/routes/$lang/index.tsx +7 -0
  83. package/dist/templates/demo-store/app/routes/$lang/journal/$journalHandle.tsx +7 -0
  84. package/dist/templates/demo-store/app/routes/$lang/journal/index.tsx +1 -0
  85. package/dist/templates/demo-store/app/routes/$lang/og-image.tsx +1 -0
  86. package/dist/templates/demo-store/app/routes/$lang/pages/$pageHandle.tsx +1 -0
  87. package/dist/templates/demo-store/app/routes/$lang/policies/$policyHandle.tsx +1 -0
  88. package/dist/templates/demo-store/app/routes/$lang/policies/index.tsx +1 -0
  89. package/dist/templates/demo-store/app/routes/$lang/products/$productHandle.tsx +6 -0
  90. package/dist/templates/demo-store/app/routes/$lang/products/index.tsx +1 -0
  91. package/dist/templates/demo-store/app/routes/$lang/search.tsx +6 -0
  92. package/dist/templates/demo-store/app/routes/[robots.txt].tsx +40 -0
  93. package/dist/templates/demo-store/app/routes/[sitemap.xml].tsx +198 -0
  94. package/dist/templates/demo-store/app/routes/account/__private/address/$id.tsx +320 -0
  95. package/dist/templates/demo-store/app/routes/account/__private/edit.tsx +273 -0
  96. package/dist/templates/demo-store/app/routes/account/__private/logout.ts +29 -0
  97. package/dist/templates/demo-store/app/routes/account/__private/orders.$id.tsx +324 -0
  98. package/dist/templates/demo-store/app/routes/account/__public/activate.$id.$activationToken.tsx +218 -0
  99. package/dist/templates/demo-store/app/routes/account/__public/login.tsx +197 -0
  100. package/dist/templates/demo-store/app/routes/account/__public/recover.tsx +144 -0
  101. package/dist/templates/demo-store/app/routes/account/__public/register.tsx +184 -0
  102. package/dist/templates/demo-store/app/routes/account/__public/reset.$id.$resetToken.tsx +214 -0
  103. package/dist/templates/demo-store/app/routes/account.tsx +191 -0
  104. package/dist/templates/demo-store/app/routes/api/countries.tsx +22 -0
  105. package/dist/templates/demo-store/app/routes/api/products.tsx +116 -0
  106. package/dist/templates/demo-store/app/routes/cart.tsx +498 -0
  107. package/dist/templates/demo-store/app/routes/collections/$collectionHandle.tsx +308 -0
  108. package/dist/templates/demo-store/app/routes/collections/all.tsx +5 -0
  109. package/dist/templates/demo-store/app/routes/collections/index.tsx +195 -0
  110. package/dist/templates/demo-store/app/routes/discounts.$code.tsx +60 -0
  111. package/dist/templates/demo-store/app/routes/featured-products.tsx +58 -0
  112. package/dist/templates/demo-store/app/routes/index.tsx +254 -0
  113. package/dist/templates/demo-store/app/routes/journal/$journalHandle.tsx +147 -0
  114. package/dist/templates/demo-store/app/routes/journal/index.tsx +150 -0
  115. package/dist/templates/demo-store/app/routes/og-image.tsx +19 -0
  116. package/dist/templates/demo-store/app/routes/pages/$pageHandle.tsx +82 -0
  117. package/dist/templates/demo-store/app/routes/policies/$policyHandle.tsx +117 -0
  118. package/dist/templates/demo-store/app/routes/policies/index.tsx +104 -0
  119. package/dist/templates/demo-store/app/routes/products/$productHandle.tsx +561 -0
  120. package/dist/templates/demo-store/app/routes/products/index.tsx +155 -0
  121. package/dist/templates/demo-store/app/routes/search.tsx +205 -0
  122. package/dist/templates/demo-store/app/styles/custom-font.css +13 -0
  123. package/dist/templates/demo-store/package-lock.json +25515 -0
  124. package/dist/templates/demo-store/package.json +67 -0
  125. package/dist/templates/demo-store/playwright.config.ts +109 -0
  126. package/dist/templates/demo-store/postcss.config.js +10 -0
  127. package/dist/templates/demo-store/public/favicon.svg +28 -0
  128. package/dist/templates/demo-store/public/fonts/IBMPlexSerif-Text.woff2 +0 -0
  129. package/dist/templates/demo-store/public/fonts/IBMPlexSerif-TextItalic.woff2 +0 -0
  130. package/dist/templates/demo-store/remix.config.js +12 -0
  131. package/dist/templates/demo-store/remix.env.d.ts +34 -0
  132. package/dist/templates/demo-store/remix.init/index.ts +15 -0
  133. package/dist/templates/demo-store/remix.init/package.json +7 -0
  134. package/dist/templates/demo-store/server.ts +87 -0
  135. package/dist/templates/demo-store/styles/app.css +182 -0
  136. package/dist/templates/demo-store/tailwind.config.js +70 -0
  137. package/dist/templates/demo-store/tests/cart.test.ts +70 -0
  138. package/dist/templates/demo-store/tests/seo.test.ts +36 -0
  139. package/dist/templates/demo-store/tests/utils.ts +100 -0
  140. package/dist/templates/demo-store/tsconfig.json +26 -0
  141. package/dist/templates/hello-world/.eslintignore +4 -0
  142. package/dist/templates/hello-world/.eslintrc.js +6 -0
  143. package/dist/templates/hello-world/.graphqlrc.yml +1 -0
  144. package/dist/templates/hello-world/.turbo/turbo-build.log +9 -0
  145. package/dist/templates/hello-world/README.md +20 -0
  146. package/dist/templates/hello-world/app/components/Layout.tsx +15 -0
  147. package/dist/templates/hello-world/app/components/index.ts +1 -0
  148. package/dist/templates/hello-world/app/entry.client.tsx +4 -0
  149. package/dist/templates/hello-world/app/entry.server.tsx +21 -0
  150. package/dist/templates/hello-world/app/root.tsx +212 -0
  151. package/dist/templates/hello-world/app/routes/index.tsx +7 -0
  152. package/dist/templates/hello-world/app/styles/app.css +38 -0
  153. package/dist/templates/hello-world/package-lock.json +27641 -0
  154. package/dist/templates/hello-world/package.json +41 -0
  155. package/dist/templates/hello-world/public/favicon.svg +28 -0
  156. package/dist/templates/hello-world/remix.env.d.ts +29 -0
  157. package/dist/templates/hello-world/server.ts +127 -0
  158. package/dist/templates/hello-world/tsconfig.json +25 -0
  159. package/dist/utils/config.js +81 -0
  160. package/dist/utils/flags.js +15 -0
  161. package/dist/utils/log.js +20 -0
  162. package/dist/utils/mini-oxygen.js +70 -0
  163. package/package.json +27 -64
  164. package/tmp-create-app.mjs +29 -0
  165. package/LICENSE +0 -8
  166. package/README.md +0 -63
  167. package/dist/cli/commands/hydrogen/add/eslint.d.ts +0 -11
  168. package/dist/cli/commands/hydrogen/add/eslint.js +0 -26
  169. package/dist/cli/commands/hydrogen/add/eslint.js.map +0 -1
  170. package/dist/cli/commands/hydrogen/add/tailwind.d.ts +0 -11
  171. package/dist/cli/commands/hydrogen/add/tailwind.js +0 -26
  172. package/dist/cli/commands/hydrogen/add/tailwind.js.map +0 -1
  173. package/dist/cli/commands/hydrogen/build.d.ts +0 -14
  174. package/dist/cli/commands/hydrogen/build.js +0 -49
  175. package/dist/cli/commands/hydrogen/build.js.map +0 -1
  176. package/dist/cli/commands/hydrogen/deploy.d.ts +0 -19
  177. package/dist/cli/commands/hydrogen/deploy.js +0 -58
  178. package/dist/cli/commands/hydrogen/deploy.js.map +0 -1
  179. package/dist/cli/commands/hydrogen/dev.d.ts +0 -13
  180. package/dist/cli/commands/hydrogen/dev.js +0 -31
  181. package/dist/cli/commands/hydrogen/dev.js.map +0 -1
  182. package/dist/cli/commands/hydrogen/info.d.ts +0 -12
  183. package/dist/cli/commands/hydrogen/info.js +0 -28
  184. package/dist/cli/commands/hydrogen/info.js.map +0 -1
  185. package/dist/cli/commands/hydrogen/preview.d.ts +0 -13
  186. package/dist/cli/commands/hydrogen/preview.js +0 -46
  187. package/dist/cli/commands/hydrogen/preview.js.map +0 -1
  188. package/dist/cli/constants.d.ts +0 -15
  189. package/dist/cli/constants.js +0 -16
  190. package/dist/cli/constants.js.map +0 -1
  191. package/dist/cli/flags.d.ts +0 -4
  192. package/dist/cli/flags.js +0 -16
  193. package/dist/cli/flags.js.map +0 -1
  194. package/dist/cli/models/hydrogen.d.ts +0 -22
  195. package/dist/cli/models/hydrogen.js +0 -82
  196. package/dist/cli/models/hydrogen.js.map +0 -1
  197. package/dist/cli/prompts/git-init.d.ts +0 -1
  198. package/dist/cli/prompts/git-init.js +0 -16
  199. package/dist/cli/prompts/git-init.js.map +0 -1
  200. package/dist/cli/services/build/check-lockfile.d.ts +0 -3
  201. package/dist/cli/services/build/check-lockfile.js +0 -80
  202. package/dist/cli/services/build/check-lockfile.js.map +0 -1
  203. package/dist/cli/services/build.d.ts +0 -14
  204. package/dist/cli/services/build.js +0 -44
  205. package/dist/cli/services/build.js.map +0 -1
  206. package/dist/cli/services/deploy/config.d.ts +0 -4
  207. package/dist/cli/services/deploy/config.js +0 -49
  208. package/dist/cli/services/deploy/config.js.map +0 -1
  209. package/dist/cli/services/deploy/error.d.ts +0 -4
  210. package/dist/cli/services/deploy/error.js +0 -11
  211. package/dist/cli/services/deploy/error.js.map +0 -1
  212. package/dist/cli/services/deploy/graphql/create_deployment.d.ts +0 -10
  213. package/dist/cli/services/deploy/graphql/create_deployment.js +0 -15
  214. package/dist/cli/services/deploy/graphql/create_deployment.js.map +0 -1
  215. package/dist/cli/services/deploy/graphql/upload_deployment.d.ts +0 -1
  216. package/dist/cli/services/deploy/graphql/upload_deployment.js +0 -16
  217. package/dist/cli/services/deploy/graphql/upload_deployment.js.map +0 -1
  218. package/dist/cli/services/deploy/types.d.ts +0 -37
  219. package/dist/cli/services/deploy/types.js +0 -2
  220. package/dist/cli/services/deploy/types.js.map +0 -1
  221. package/dist/cli/services/deploy/upload.d.ts +0 -5
  222. package/dist/cli/services/deploy/upload.js +0 -81
  223. package/dist/cli/services/deploy/upload.js.map +0 -1
  224. package/dist/cli/services/deploy.d.ts +0 -2
  225. package/dist/cli/services/deploy.js +0 -103
  226. package/dist/cli/services/deploy.js.map +0 -1
  227. package/dist/cli/services/dev/check-version.d.ts +0 -1
  228. package/dist/cli/services/dev/check-version.js +0 -30
  229. package/dist/cli/services/dev/check-version.js.map +0 -1
  230. package/dist/cli/services/dev.d.ts +0 -10
  231. package/dist/cli/services/dev.js +0 -36
  232. package/dist/cli/services/dev.js.map +0 -1
  233. package/dist/cli/services/eslint.d.ts +0 -8
  234. package/dist/cli/services/eslint.js +0 -74
  235. package/dist/cli/services/eslint.js.map +0 -1
  236. package/dist/cli/services/info.d.ts +0 -7
  237. package/dist/cli/services/info.js +0 -131
  238. package/dist/cli/services/info.js.map +0 -1
  239. package/dist/cli/services/preview.d.ts +0 -12
  240. package/dist/cli/services/preview.js +0 -63
  241. package/dist/cli/services/preview.js.map +0 -1
  242. package/dist/cli/services/tailwind.d.ts +0 -9
  243. package/dist/cli/services/tailwind.js +0 -103
  244. package/dist/cli/services/tailwind.js.map +0 -1
  245. package/dist/cli/utilities/load-config.d.ts +0 -5
  246. package/dist/cli/utilities/load-config.js +0 -6
  247. package/dist/cli/utilities/load-config.js.map +0 -1
  248. package/dist/tsconfig.tsbuildinfo +0 -1
  249. package/oclif.manifest.json +0 -1
@@ -0,0 +1,70 @@
1
+ import {test, expect} from '@playwright/test';
2
+ import {waitForLoaders} from './utils';
3
+
4
+ test.describe('Cart', () => {
5
+ test('From home to checkout flow', async ({page}) => {
6
+ // Home => Collections => First collection => First product
7
+ await page.goto(`/`);
8
+ await page.locator(`header nav a:text-is("Collections")`).click();
9
+ await page.locator(`[data-test=collection-grid] a >> nth=0`).click();
10
+ await page.locator(`[data-test=product-grid] a >> nth=0`).click();
11
+ await page.locator(`[data-test=add-to-cart]`).click();
12
+
13
+ await waitForLoaders(page, () =>
14
+ page.locator(`button :text-is("+")`).click({clickCount: 1, delay: 600}),
15
+ );
16
+
17
+ await expect(
18
+ page.locator('[data-test=item-quantity]'),
19
+ 'should increase quantity',
20
+ ).toContainText('2');
21
+
22
+ // Close cart drawer => Products => First product
23
+ await page.locator('[data-test=close-cart]').click();
24
+ await page.locator(`header nav a:text-is("Products")`).click();
25
+ await page.locator(`[data-test=product-grid] a >> nth=0`).click();
26
+
27
+ await waitForLoaders(page, () =>
28
+ page.locator(`[data-test=add-to-cart]`).click(),
29
+ );
30
+
31
+ const quantities = await page
32
+ .locator('[data-test=item-quantity]')
33
+ .allTextContents();
34
+
35
+ await expect(
36
+ quantities.reduce((a, b) => a + Number(b), 0),
37
+ 'should have the correct item quantities',
38
+ ).toEqual(3);
39
+
40
+ const priceInStore = await page
41
+ .locator('[data-test=subtotal]')
42
+ .textContent();
43
+
44
+ await page.locator('a :text("Checkout")').click();
45
+
46
+ await expect(page.url(), 'should navigate to checkout').toMatch(
47
+ /[\w\d-]+\.myshopify\.com\/\d+\/checkouts\/[\d\w]+/,
48
+ );
49
+
50
+ const priceInCheckout = await page
51
+ .locator('[data-checkout-subtotal-price-target]')
52
+ .textContent();
53
+
54
+ await expect(
55
+ normalizePrice(priceInCheckout),
56
+ 'should show the same price in checkout',
57
+ ).toEqual(normalizePrice(priceInStore));
58
+ });
59
+ });
60
+
61
+ function normalizePrice(price: string | null) {
62
+ if (!price) throw new Error('Price was not found');
63
+
64
+ return price
65
+ .replace('$', '')
66
+ .trim()
67
+ .replace(/[.,](\d\d)$/, '-$1')
68
+ .replace(/[.,]/g, '')
69
+ .replace('-', '.');
70
+ }
@@ -0,0 +1,36 @@
1
+ import {test, expect} from '@playwright/test';
2
+
3
+ test.describe('home', () => {
4
+ test('has meta elements for description, title and robots', async ({
5
+ page,
6
+ }) => {
7
+ const text = {
8
+ robots: 'index,follow',
9
+ title: 'Hydrogen',
10
+ description:
11
+ "A custom storefront powered by Hydrogen, Shopify's React-based framework for building headless.",
12
+ };
13
+ const entries = [
14
+ [
15
+ 'description',
16
+ [
17
+ 'meta[name="description"]',
18
+ 'meta[property="og:description"]',
19
+ 'meta[name="twitter:description"]',
20
+ ],
21
+ ],
22
+ ['robots', ['meta[name="robots"]', 'meta[name="googlebot"]']],
23
+ ['title', ['meta[property="og:title"]', 'meta[name="twitter:title"]']],
24
+ ];
25
+
26
+ await page.goto(`/`);
27
+
28
+ for (const [key, elements] of entries) {
29
+ const expected = text[key as keyof typeof text];
30
+ for (const element of elements) {
31
+ const meta = await page.locator(element);
32
+ await expect(meta).toHaveAttribute('content', expected);
33
+ }
34
+ }
35
+ });
36
+ });
@@ -0,0 +1,100 @@
1
+ // eslint-disable-next-line eslint-comments/disable-enable-pair
2
+ /* eslint-disable no-console */
3
+
4
+ import type {Page, Request} from '@playwright/test';
5
+
6
+ /**
7
+ * @description Wait until the Root loader is refreshed after a Form action
8
+ * @param page Page
9
+ * @param action Fn
10
+ */
11
+ export async function waitForLoaders(page: Page, action: () => Promise<any>) {
12
+ return waitForNetworkSettled(
13
+ page,
14
+ action,
15
+ (request) => /\?_data=root/.test(request.url()),
16
+ 1,
17
+ );
18
+ }
19
+
20
+ // Based on https://gist.github.com/dgozman/d1c46f966eb9854ee1fe24960b603b28
21
+ const DEBUG = false;
22
+ export async function waitForNetworkSettled(
23
+ page: Page,
24
+ action: () => Promise<any>,
25
+ requestFilter?: (request: Request) => boolean,
26
+ minimumRequests = 0,
27
+ ) {
28
+ const skipRequest = (request: Request) =>
29
+ !!requestFilter && !requestFilter(request);
30
+
31
+ let networkSettledCallback: (v?: unknown) => void;
32
+ const networkSettledPromise = new Promise(
33
+ (f) => (networkSettledCallback = f),
34
+ );
35
+
36
+ let requestCounter = 0;
37
+ let actionDone = false;
38
+ const pending = new Set<Request>();
39
+
40
+ const maybeSettle = () => {
41
+ if (actionDone && requestCounter <= 0 && minimumRequests <= 0)
42
+ networkSettledCallback();
43
+ };
44
+
45
+ const onRequest = (request: Request) => {
46
+ if (skipRequest(request)) return;
47
+
48
+ ++requestCounter;
49
+ DEBUG && pending.add(request);
50
+ DEBUG &&
51
+ console.log(`+[${requestCounter}]: ${request.method()} ${request.url()}`);
52
+ };
53
+ const onRequestDone = (request: Request) => {
54
+ if (skipRequest(request)) return;
55
+
56
+ // Let the page handle responses asynchronously (via setTimeout(0)).
57
+ //
58
+ // Note: this might be changed to use delay, e.g. setTimeout(f, 100),
59
+ // when the page uses delay itself.
60
+ const evaluate = page.evaluate(() => new Promise((f) => setTimeout(f, 0)));
61
+ evaluate
62
+ .catch((e) => null)
63
+ .then(() => {
64
+ --requestCounter;
65
+ --minimumRequests;
66
+ maybeSettle();
67
+ DEBUG && pending.delete(request);
68
+ DEBUG &&
69
+ console.log(
70
+ `-[${requestCounter}]: ${request.method()} ${request.url()}`,
71
+ );
72
+ });
73
+ };
74
+
75
+ page.on('request', onRequest);
76
+ page.on('requestfinished', onRequestDone);
77
+ page.on('requestfailed', onRequestDone);
78
+
79
+ let timeoutId;
80
+ DEBUG &&
81
+ (timeoutId = setInterval(() => {
82
+ console.log(`${requestCounter} requests pending:`);
83
+ for (const request of pending) console.log(` ${request.url()}`);
84
+ }, 5000));
85
+
86
+ const result = await action();
87
+ actionDone = true;
88
+ maybeSettle();
89
+ DEBUG && console.log(`action done, ${requestCounter} requests pending`);
90
+ await networkSettledPromise;
91
+ DEBUG && console.log(`action done, network settled`);
92
+
93
+ page.removeListener('request', onRequest);
94
+ page.removeListener('requestfinished', onRequestDone);
95
+ page.removeListener('requestfailed', onRequestDone);
96
+
97
+ DEBUG && clearTimeout(timeoutId);
98
+
99
+ return result;
100
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"],
3
+ "compilerOptions": {
4
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
5
+ "isolatedModules": true,
6
+ "esModuleInterop": true,
7
+ "jsx": "react-jsx",
8
+ "moduleResolution": "node",
9
+ "resolveJsonModule": true,
10
+ "target": "ES2022",
11
+ "strict": true,
12
+ "allowJs": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "skipLibCheck": true,
15
+ "baseUrl": ".",
16
+ "types": ["@shopify/oxygen-workers-types"],
17
+ "paths": {
18
+ "~/*": ["app/*"],
19
+ ".hydrogen/*": [".hydrogen/*"]
20
+ },
21
+
22
+ // Remix takes care of building everything in `./app` with `remix build`.
23
+ // Wrangler takes care of building everything in `./worker` with `wrangler start` / `wrangler publish`.
24
+ "noEmit": true
25
+ }
26
+ }
@@ -0,0 +1,4 @@
1
+ build
2
+ node_modules
3
+ bin
4
+ *.d.ts
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @type {import("@types/eslint").Linter.BaseConfig}
3
+ */
4
+ module.exports = {
5
+ extends: ['plugin:hydrogen/recommended', 'plugin:hydrogen/typescript'],
6
+ };
@@ -0,0 +1 @@
1
+ schema: node_modules/@shopify/hydrogen-react/storefront.schema.json
@@ -0,0 +1,9 @@
1
+
2
+ > hello-world@0.0.0 build
3
+ > shopify hydrogen build --entry ./server
4
+
5
+
6
+ 🏗️ Building in production mode...
7
+ 📦 Worker built: 474.459ms
8
+ dist/worker/index.js 0.16 MB
9
+
@@ -0,0 +1,20 @@
1
+ # Hydrogen template: Hello World
2
+
3
+ A minimal setup of components, queries and tooling to get started with Hydrogen.
4
+
5
+ ## What's included
6
+
7
+ - ESLint
8
+ - Prettier
9
+ - Typescript
10
+ - Oxygen
11
+
12
+ ## TODO
13
+
14
+ - Everything in `lib` folder will maybe need to be abstracted into a shared module
15
+
16
+ ## Questions
17
+
18
+ - Should we add tailwind css?
19
+ - Should we do bot detection?
20
+ - Rename `oxygen.ts` to `server.ts`
@@ -0,0 +1,15 @@
1
+ interface LayoutProps {
2
+ children?: React.ReactNode;
3
+ title?: string;
4
+ description?: string | null;
5
+ }
6
+
7
+ export function Layout({children, title, description}: LayoutProps) {
8
+ return (
9
+ <div className="Layout">
10
+ <h1>{title}</h1>
11
+ <h2>{description}</h2>
12
+ {children}
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1 @@
1
+ export {Layout} from './Layout';
@@ -0,0 +1,4 @@
1
+ import {RemixBrowser} from '@remix-run/react';
2
+ import {hydrateRoot} from 'react-dom/client';
3
+
4
+ hydrateRoot(document, <RemixBrowser />);
@@ -0,0 +1,21 @@
1
+ import type {EntryContext} from '@shopify/remix-oxygen';
2
+ import {RemixServer} from '@remix-run/react';
3
+ import {renderToString} from 'react-dom/server';
4
+
5
+ export default function handleRequest(
6
+ request: Request,
7
+ responseStatusCode: number,
8
+ responseHeaders: Headers,
9
+ remixContext: EntryContext,
10
+ ) {
11
+ const markup = renderToString(
12
+ <RemixServer context={remixContext} url={request.url} />,
13
+ );
14
+
15
+ responseHeaders.set('Content-Type', 'text/html');
16
+
17
+ return new Response('<!DOCTYPE html>' + markup, {
18
+ status: responseStatusCode,
19
+ headers: responseHeaders,
20
+ });
21
+ }
@@ -0,0 +1,212 @@
1
+ import {
2
+ defer,
3
+ type LinksFunction,
4
+ type MetaFunction,
5
+ type LoaderArgs,
6
+ } from '@shopify/remix-oxygen';
7
+ import {
8
+ Links,
9
+ Meta,
10
+ Outlet,
11
+ Scripts,
12
+ ScrollRestoration,
13
+ useLoaderData,
14
+ } from '@remix-run/react';
15
+ import {Cart, Shop} from '@shopify/hydrogen-react/storefront-api-types';
16
+ import {Layout} from '~/components';
17
+ import styles from './styles/app.css';
18
+ import favicon from '../public/favicon.svg';
19
+
20
+ export const links: LinksFunction = () => {
21
+ return [
22
+ {rel: 'stylesheet', href: styles},
23
+ {
24
+ rel: 'preconnect',
25
+ href: 'https://cdn.shopify.com',
26
+ },
27
+ {
28
+ rel: 'preconnect',
29
+ href: 'https://shop.app',
30
+ },
31
+ {rel: 'icon', type: 'image/svg+xml', href: favicon},
32
+ ];
33
+ };
34
+
35
+ export const meta: MetaFunction = (data) => ({
36
+ charset: 'utf-8',
37
+ viewport: 'width=device-width,initial-scale=1',
38
+ });
39
+
40
+ export async function loader({context, request}: LoaderArgs) {
41
+ const cartId = await context.session.get('cartId');
42
+
43
+ const [cart, layout] = await Promise.all([
44
+ cartId
45
+ ? (
46
+ await context.storefront.query<{cart: Cart}>(CART_QUERY, {
47
+ variables: {
48
+ cartId,
49
+ /**
50
+ Country and language properties are automatically injected
51
+ into all queries. Passing them is unnecessary unless you
52
+ want to override them from the following default:
53
+ */
54
+ country: context.storefront.i18n?.country,
55
+ language: context.storefront.i18n?.language,
56
+ },
57
+ cache: context.storefront.CacheNone(),
58
+ })
59
+ ).cart
60
+ : null,
61
+ await context.storefront.query<{shop: Shop}>(LAYOUT_QUERY),
62
+ ]);
63
+
64
+ return defer({
65
+ cart,
66
+ layout,
67
+ });
68
+ }
69
+
70
+ export default function App() {
71
+ const data = useLoaderData<typeof loader>();
72
+
73
+ const {name, description} = data.layout.shop;
74
+
75
+ return (
76
+ <html lang="en">
77
+ <head>
78
+ <Meta />
79
+ <Links />
80
+ </head>
81
+ <body>
82
+ <Layout description={description} title={name}>
83
+ <Outlet />
84
+ </Layout>
85
+ <ScrollRestoration />
86
+ <Scripts />
87
+ </body>
88
+ </html>
89
+ );
90
+ }
91
+
92
+ const CART_QUERY = `#graphql
93
+ query CartQuery($cartId: ID!) {
94
+ cart(id: $cartId) {
95
+ ...CartFragment
96
+ }
97
+ }
98
+
99
+ fragment CartFragment on Cart {
100
+ id
101
+ checkoutUrl
102
+ totalQuantity
103
+ buyerIdentity {
104
+ countryCode
105
+ customer {
106
+ id
107
+ email
108
+ firstName
109
+ lastName
110
+ displayName
111
+ }
112
+ email
113
+ phone
114
+ }
115
+ lines(first: 100) {
116
+ edges {
117
+ node {
118
+ id
119
+ quantity
120
+ attributes {
121
+ key
122
+ value
123
+ }
124
+ cost {
125
+ totalAmount {
126
+ amount
127
+ currencyCode
128
+ }
129
+ amountPerQuantity {
130
+ amount
131
+ currencyCode
132
+ }
133
+ compareAtAmountPerQuantity {
134
+ amount
135
+ currencyCode
136
+ }
137
+ }
138
+ merchandise {
139
+ ... on ProductVariant {
140
+ id
141
+ availableForSale
142
+ compareAtPrice {
143
+ ...MoneyFragment
144
+ }
145
+ price {
146
+ ...MoneyFragment
147
+ }
148
+ requiresShipping
149
+ title
150
+ image {
151
+ ...ImageFragment
152
+ }
153
+ product {
154
+ handle
155
+ title
156
+ id
157
+ }
158
+ selectedOptions {
159
+ name
160
+ value
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ cost {
168
+ subtotalAmount {
169
+ ...MoneyFragment
170
+ }
171
+ totalAmount {
172
+ ...MoneyFragment
173
+ }
174
+ totalDutyAmount {
175
+ ...MoneyFragment
176
+ }
177
+ totalTaxAmount {
178
+ ...MoneyFragment
179
+ }
180
+ }
181
+ note
182
+ attributes {
183
+ key
184
+ value
185
+ }
186
+ discountCodes {
187
+ code
188
+ }
189
+ }
190
+
191
+ fragment MoneyFragment on MoneyV2 {
192
+ currencyCode
193
+ amount
194
+ }
195
+
196
+ fragment ImageFragment on Image {
197
+ id
198
+ url
199
+ altText
200
+ width
201
+ height
202
+ }
203
+ `;
204
+
205
+ const LAYOUT_QUERY = `#graphql
206
+ query layout {
207
+ shop {
208
+ name
209
+ description
210
+ }
211
+ }
212
+ `;
@@ -0,0 +1,7 @@
1
+ export default function Index() {
2
+ return (
3
+ <p>
4
+ Edit this route in <em>app/routes/index.tsx</em>.
5
+ </p>
6
+ );
7
+ }
@@ -0,0 +1,38 @@
1
+ body {
2
+ padding: 0;
3
+ margin: 0;
4
+ background: rgb(245, 245, 241);
5
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
6
+ Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
7
+ }
8
+
9
+ h1,
10
+ h2,
11
+ p {
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ h1 {
17
+ font-size: 1.6rem;
18
+ font-weight: 700;
19
+ margin-bottom: 1rem;
20
+ line-height: 1.4;
21
+ }
22
+
23
+ h2 {
24
+ font-size: 1.2rem;
25
+ font-weight: 700;
26
+ margin-bottom: 1rem;
27
+ line-height: 1.4;
28
+ }
29
+
30
+ p {
31
+ font-size: 1rem;
32
+ line-height: 1.4;
33
+ }
34
+
35
+ .Layout {
36
+ padding: 2rem;
37
+ max-width: 25rem;
38
+ }