From ceb392c9830f588a98723bdbfa541905f8731186 Mon Sep 17 00:00:00 2001 From: Robert Reinhard Date: Fri, 27 May 2022 08:49:33 -0700 Subject: [PATCH 01/20] Adding a product query with pdp variable --- helpers/query.coffee | 16 ++++------------ queries/product.gql | 12 ++++++++++++ 2 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 queries/product.gql diff --git a/helpers/query.coffee b/helpers/query.coffee index b5506f6..01825ec 100644 --- a/helpers/query.coffee +++ b/helpers/query.coffee @@ -10,6 +10,7 @@ import updateQuery from '../queries/update.gql' import deleteQuery from '../queries/delete.gql' import discountsQuery from '../queries/discounts.gql' import linkCustomerQuery from '../queries/link-customer.gql' +import productQuery from '../queries/product.gql' import productFragment from '../queries/fragments/product.gql' # Throw errors if found in the response, else map the checkout object @@ -95,20 +96,11 @@ export linkCustomer = ({ execute }, { cartId, accessToken }) -> cartId: cartId buyerIdentity: customerAccessToken: accessToken -# Get product data for a PDP by adding a couple additional feilds onto the main -# product fragment +# Get product data for a PDP export getProductDetail = ({ execute }, handle) -> { product } = await execute - variables: { handle } - query: """ - query getProductDetail($handle: String!) { - product: productByHandle(handle:$handle) { - ...product - description: descriptionHtml - } - } - #{productFragment} - """ + variables: { handle, pdp: true } + query: productQuery return product # Helper to look up product card data from their handles by querying the diff --git a/queries/product.gql b/queries/product.gql new file mode 100644 index 0000000..ae83c49 --- /dev/null +++ b/queries/product.gql @@ -0,0 +1,12 @@ +#import "./fragments/product.gql" + +# Fetch a single product. $pdp can be used to add fields needed for rendering +# on the PDP, like the description +query ($handle: String!, $pdp: Boolean = false) { + product: productByHandle(handle:$handle) { + ...product + + # PDP specific fields + description: descriptionHtml @include(if: $pdp) + } +} From 79fc3f5edccbe85d6253bc378a68ad8b7444ed47 Mon Sep 17 00:00:00 2001 From: Robert Reinhard Date: Fri, 27 May 2022 08:59:36 -0700 Subject: [PATCH 02/20] Add merge helpers --- demo/components/merge-demo.vue | 38 ++++++++++++++++++++++ demo/content/demo.md | 42 +++++++++++++++++++----- helpers/formatting.coffee | 4 +++ helpers/merge.coffee | 58 ++++++++++++++++++++++++++++++++++ nuxt.js | 2 +- plugins/storefront-client.js | 9 ++++++ yarn.lock | 2 +- 7 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 demo/components/merge-demo.vue create mode 100644 helpers/formatting.coffee create mode 100644 helpers/merge.coffee diff --git a/demo/components/merge-demo.vue b/demo/components/merge-demo.vue new file mode 100644 index 0000000..bfe394c --- /dev/null +++ b/demo/components/merge-demo.vue @@ -0,0 +1,38 @@ + + + + + + + + + + + diff --git a/demo/content/demo.md b/demo/content/demo.md index fe15cef..1b873d6 100644 --- a/demo/content/demo.md +++ b/demo/content/demo.md @@ -24,16 +24,42 @@ export default data: -> products: [] - fetch: -> - { @products } = await @$storefront.execute query: ''' - query getSomeProducts { - products(first: 5) { - edges { - node { ...product } - } + fetch: -> { @products } = await @$storefront.execute query: ''' + query getSomeProducts { + products(first: 5) { + edges { + node { ...product } } } - ''' + productFragment + } + ''' + productFragment + + +``` + +## Merge Example + +This uses live Shopify data and the `$mergeShopifyProductCards` helper to simulate merging Shopify data into an array of product data from another source, like Craft. + + + +```vue + + + ``` diff --git a/helpers/formatting.coffee b/helpers/formatting.coffee new file mode 100644 index 0000000..0d99a93 --- /dev/null +++ b/helpers/formatting.coffee @@ -0,0 +1,4 @@ +# Get the id from a Shoify gid:// style id. This strips everything but the +# last part of the string. So gid://shopify/ProductVariant/34641879105581 +# becomes 34641879105581 +export getShopifyId = (id) -> id.match(/\/(\w+)$/)?[1] diff --git a/helpers/merge.coffee b/helpers/merge.coffee new file mode 100644 index 0000000..b5f5b83 --- /dev/null +++ b/helpers/merge.coffee @@ -0,0 +1,58 @@ +### +Helpers related to mering Shopify product data into other product objects by +slug. Intended use case is when we get product listing info from Craft, designed +for rendering in cards. +### +import memoize from 'lodash/memoize' +import { getShopifyId } from './formatting' +import getProduct from '../queries/product.gql' + +# Take an array of Craft product entries and merge Shopify data with them. This +# preserves the original order of the products. +export mergeShopifyProductCards = ({ execute }, products) -> + return [] unless products.length + + # Get Shopify and bundle data for products. If Shopify data isn't found, + # an exception is thrown, which we catch and use to remove that product + # from the listing. + products = await Promise.all products.map (product) -> + try await mergeShopifyProductCard { execute }, product + catch error then console.warn error + + # Remove all empty products (those that errored while getting data) + return products.filter (val) -> !!val + +# Merge Shopify data into a single card +export mergeShopifyProductCard = memoize ({ execute }, product) -> + return unless product + + # Merge Shopify data into the product object + shopifyProduct = await getShopifyProductByHandle { execute }, product.slug + product = { ...product, ...shopifyProduct } + + # Remove keys not needed for cards + delete product.description + + # Return the final product + return product + +# Use the slug as the memoize resolver +, (deps, product) -> product?.slug + +# Get the Shopify product data given a Shopify product handle +getShopifyProductByHandle = ({ execute }, productHandle) -> + + # Query Storefront API + { product } = await execute + query: getProduct + variables: handle: productHandle + unless product then throw "No Shopify product found for #{productHandle}" + + # Add URLs to each variant for easier iteration later + product.variants = product.variants.map (variant) => + variantIdNum = getShopifyId variant.id + url = "/products/#{productHandle}/#{variantIdNum}" + return { ...variant, url } + + # Return the product + return product diff --git a/nuxt.js b/nuxt.js index bf3e83a..c625450 100644 --- a/nuxt.js +++ b/nuxt.js @@ -10,7 +10,7 @@ export default function() { url: process.env.SHOPIFY_URL, storefront: { token: process.env.SHOPIFY_STOREFRONT_TOKEN, - version: 'unstable', + version: '2022-04', injectClient: true, }, mocks: [], diff --git a/plugins/storefront-client.js b/plugins/storefront-client.js index 5798c64..b4a42bb 100644 --- a/plugins/storefront-client.js +++ b/plugins/storefront-client.js @@ -2,6 +2,7 @@ * Create the Storefront axios client instance */ import { makeStorefrontClient } from '../factories' +import * as mergeHelpers from '../helpers/merge' import * as queryHelpers from '../helpers/query' export default function({ $axios, $config }, inject) { @@ -22,5 +23,13 @@ export default function({ $axios, $config }, inject) { } }) + // Injeect the final Storefront object inject('storefront', $storefront) + + // Inject merge helpers, which rely on the storefront object + Object.entries(mergeHelpers).forEach(([methodName, method]) => { + inject(methodName, (...args) => { + return method.apply(null, [$storefront, ...args]) + }) + }) } diff --git a/yarn.lock b/yarn.lock index 793adad..671dc24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10662,7 +10662,7 @@ webpack-dev-middleware@^4.2.0: range-parser "^1.2.1" schema-utils "^3.0.0" -"webpack-graphql-loader@github:gpoitch/graphql-loader#gp/refresh", webpack-graphql-loader@gpoitch/graphql-loader#gp/refresh: +webpack-graphql-loader@gpoitch/graphql-loader#gp/refresh: version "1.0.2" resolved "https://codeload.github.com/gpoitch/graphql-loader/tar.gz/42f0f990a6132ccd5e284d0e6cfeade12728dba2" dependencies: From 85f092e4d323c570747d5a2ed2a4475d924acff0 Mon Sep 17 00:00:00 2001 From: Robert Reinhard Date: Fri, 27 May 2022 09:10:29 -0700 Subject: [PATCH 03/20] Update documented default version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd3516b..2e0bf24 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Set these properties within `cloak: { shopify: { ... } }` in the nuxt.config.js: - `url` - Your public Shopify store URL, for example: https://brand.myshopify.com or https://shop.brand.com. Defaults to `process.env.SHOPIFY_URL`. - `storefront:` - `token` - The Storefront API token of your custom app. Defaults to `process.env.SHOPIFY_STOREFRONT_TOKEN`. - - `version` - The [Storefront API version](https://shopify.dev/api/usage/versioning) to use. Defaults to `unstable` (aka, latest). + - `version` - The [Storefront API version](https://shopify.dev/api/usage/versioning) to use. Defaults to `2022-04`. - `injectClient` - Boolean for whether to inject the `$storefront` client globally. Defaults to `true`. You would set this to `false` when this module is a depedency of another module (like [@cloak-app/algolia](https://github.com/BKWLD/cloak-algolia)) that is creating `$storefront` a different way. - `mocks` - An array of objects for use with [`mockAxiosGql`](https://github.com/BKWLD/cloak-utils/blob/main/src/axios.js). From 46342d959b9b8c43fb2656e942d50614ff86c042 Mon Sep 17 00:00:00 2001 From: Robert Reinhard Date: Fri, 27 May 2022 09:12:58 -0700 Subject: [PATCH 04/20] Include a default version in factory --- factories/storefront-client-factory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/factories/storefront-client-factory.js b/factories/storefront-client-factory.js index dc48105..2c67444 100644 --- a/factories/storefront-client-factory.js +++ b/factories/storefront-client-factory.js @@ -2,7 +2,7 @@ import mapValues from 'lodash/mapValues' import isPlainObject from 'lodash/isPlainObject' // Factory method for making Storefront Axios clients -export default function (axios, { url, token, version } = {}) { +export default function (axios, { url, token, version = '2022-04' } = {}) { // Make Storefront instance const storefront = axios.create({ From c0547194c0a67d5c6978cb73b41b01d28b519026 Mon Sep 17 00:00:00 2001 From: Robert Reinhard Date: Fri, 27 May 2022 10:38:27 -0700 Subject: [PATCH 05/20] Make a merge-helpers file --- README.md | 6 ++++-- factories/merge-helpers.js | 18 ++++++++++++++++++ helpers/{merge.coffee => cards.coffee} | 0 plugins/storefront-client.js | 22 ++++++---------------- 4 files changed, 28 insertions(+), 18 deletions(-) create mode 100644 factories/merge-helpers.js rename helpers/{merge.coffee => cards.coffee} (100%) diff --git a/README.md b/README.md index 2e0bf24..ab660d1 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,12 @@ You can make an instance of the Storefront Axios client when outside of Nuxt (li ```js import { makeStorefrontClient } from '@cloak-app/shopify/factories' -const storefront = makeStorefrontClient({ +import mergeClientHelpers from '@cloak-app/shopify/factories/merge-helpers' +const storefront = mergeClientHelpers(makeStorefrontClient({ url: process.env.SHOPIFY_URL, token: process.env.SHOPIFY_STOREFRONT_TOKEN, -}) + version: '2022-04', // Optional +})) // Optional, inject it globally into Vue components import Vue from 'vue' diff --git a/factories/merge-helpers.js b/factories/merge-helpers.js new file mode 100644 index 0000000..d4c116c --- /dev/null +++ b/factories/merge-helpers.js @@ -0,0 +1,18 @@ +import * as queryHelpers from '../helpers/query' + +/** + * Merge query query helpers into the storefront object. This is done from + * a different file from the other factory files because it results in loading + * gql files that will require transpiling. And we want to make the base + * Storefront client to be useable without transpiling. + */ +export default function ($storefront) { + Object.entries(queryHelpers).forEach(([methodName, method]) => { + $storefront[methodName] = (...args) => { + return method.apply(null, [$storefront, ...args]) + } + }) + + // Return $storefront so this can wrap Storefront factory + return $storefront +} diff --git a/helpers/merge.coffee b/helpers/cards.coffee similarity index 100% rename from helpers/merge.coffee rename to helpers/cards.coffee diff --git a/plugins/storefront-client.js b/plugins/storefront-client.js index b4a42bb..fa78081 100644 --- a/plugins/storefront-client.js +++ b/plugins/storefront-client.js @@ -2,32 +2,22 @@ * Create the Storefront axios client instance */ import { makeStorefrontClient } from '../factories' -import * as mergeHelpers from '../helpers/merge' -import * as queryHelpers from '../helpers/query' +import mergeClientHelpers from '../factories/merge-helpers' +import * as cardsHelpers from '../helpers/cards' export default function({ $axios, $config }, inject) { // Make the instance - const $storefront = makeStorefrontClient({ + const $storefront = mergeQueryHelpers(makeStorefrontClient({ axios: $axios, url: $config.cloak.shopify.url, ...$config.cloak.shopify.storefront, - }) - - // Loop through query helpers and register them on the client, passing the - // axios client in as the first argument. This happens in this plugin - // rather than in the factory so we can transpile the gql used by the - // query helpers. - Object.entries(queryHelpers).forEach(([methodName, method]) => { - $storefront[methodName] = (...args) => { - return method.apply(null, [$storefront, ...args]) - } - }) + })) - // Injeect the final Storefront object + // Inject the final Storefront object inject('storefront', $storefront) // Inject merge helpers, which rely on the storefront object - Object.entries(mergeHelpers).forEach(([methodName, method]) => { + Object.entries(cardsHelpers).forEach(([methodName, method]) => { inject(methodName, (...args) => { return method.apply(null, [$storefront, ...args]) }) From 36e376c91db3d06d6185426919bd19bf16ef7aed Mon Sep 17 00:00:00 2001 From: Robert Reinhard Date: Fri, 27 May 2022 10:40:31 -0700 Subject: [PATCH 06/20] Fix function reference --- plugins/storefront-client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/storefront-client.js b/plugins/storefront-client.js index fa78081..0de73db 100644 --- a/plugins/storefront-client.js +++ b/plugins/storefront-client.js @@ -7,7 +7,7 @@ import * as cardsHelpers from '../helpers/cards' export default function({ $axios, $config }, inject) { // Make the instance - const $storefront = mergeQueryHelpers(makeStorefrontClient({ + const $storefront = mergeClientHelpers(makeStorefrontClient({ axios: $axios, url: $config.cloak.shopify.url, ...$config.cloak.shopify.storefront, From 3c622f1502f1c5e08e93ba6551a9df7797fbe4f0 Mon Sep 17 00:00:00 2001 From: Robert Reinhard Date: Fri, 27 May 2022 12:14:07 -0700 Subject: [PATCH 07/20] Adding ssg-variants to module --- demo/nuxt.config.js | 19 ++++- demo/pages/index.vue | 2 +- .../{_product.vue => _product/_variant.vue} | 0 .../product-variants-for-ssg-variants.json | 15 ++++ demo/stubs/products-for-ssg-variants.json | 10 +++ helpers/formatting.coffee | 4 - helpers/formatting.js | 7 ++ modules/ssg-variants.js | 82 +++++++++++++++++++ nuxt.js | 9 ++ package.json | 3 +- yarn.lock | 29 ++++--- 11 files changed, 161 insertions(+), 19 deletions(-) rename demo/pages/products/{_product.vue => _product/_variant.vue} (100%) create mode 100644 demo/stubs/product-variants-for-ssg-variants.json create mode 100644 demo/stubs/products-for-ssg-variants.json delete mode 100644 helpers/formatting.coffee create mode 100644 helpers/formatting.js create mode 100644 modules/ssg-variants.js diff --git a/demo/nuxt.config.js b/demo/nuxt.config.js index 472636a..f6de858 100644 --- a/demo/nuxt.config.js +++ b/demo/nuxt.config.js @@ -1,5 +1,7 @@ // Mock stubs import someProducts from './stubs/some-products.json' +import productsForSsgVariants from './stubs/products-for-ssg-variants.json' +import productVariantsForSsgVariants from './stubs/product-variants-for-ssg-variants.json' // Nuxt config export default { @@ -8,6 +10,7 @@ export default { buildModules: [ '@cloak-app/boilerplate', '@cloak-app/demo-theme', + '@cloak-app/craft', '../nuxt', ], @@ -30,10 +33,24 @@ export default { { query: 'getSomeProducts', response: someProducts, - } + }, + { + query: 'getProductVariantsForSsgVariants', + response: productVariantsForSsgVariants + }, ], }, + // Mock Craft queries + craft: { + mocks: [ + { + query: 'getProductsForSsgVariants', + response: productsForSsgVariants + } + ] + } + }, // @nuxt/content can't be loaded from module diff --git a/demo/pages/index.vue b/demo/pages/index.vue index b1714fa..a4f9e37 100644 --- a/demo/pages/index.vue +++ b/demo/pages/index.vue @@ -11,7 +11,7 @@ nuxt-content(:document='page')