Open19

ShopifyのフロントをGatsby+TypeScriptで構築

okita kamegorookita kamegoro

ShopifyのストアフロントをGatsbyで構築するのは案外良さそう。
NextとGatsbyのどちらを選択すべきかについては、またいずれ書く。
ひとまず、Udemyやブログに構築方法が紹介されているので参考にしながら構築してみる。
また、TypeScriptも導入しているようなものはあまりないので今回はそれも導入してみて、その知見をためていく。

ReactももGatsbyもTypeScriptも初めてなので、無駄なフローもあるかもしれない。

okita kamegorookita kamegoro

参考サイト

まずは特に参考になったサイトを貼っておく
公式系は当然みると思うので省略

  1. How To Set Up a Gatsby Project with TypeScript | DigitalOcean
  2. Gatsby.jsのTypeScript化 2020
  3. React Prop Types with TypeScript | Ben Ilegbodu

単に初めてのReact, TypeScriptで勉強になったサイト

  1. コンポーネントの作り方(marginの持たせ方とか)
  2. React+Typescriptのテンプレート群、割と参考にできそう。
  3. 無駄にスターの多いgatsbyベースのブログ
  4. TypeScript + Reactのチートシート
  5. 有志による?TypeScriptのチュートリアルサイト
  6. stateの型定義について

備考

  1. Shopify + Next(Storefront APIを選択した理由間違っている気がするが、後でちゃんと読み解いておきたい)

SSGで商品ページ作ればリクエスト制限を超過するようなことはないよねというお気持ち。

参考になりそうなGatsbyのスターター

UdemyでちょうどShopify+Gatsbyのサイト構築があったのでこちらをベースに参考にできそう

スターター
https://github.com/tomphill/gatsby-shopify-starter

完成版
https://github.com/tomphill/gatsby-shopify-course

一方で、上記のプロジェクトはTypeScriptに対応していないため、まずは素のGatsbyをインストールしてTypeScriptに対応=>その後にこのスターターに寄せていくような作りにしようと思う。

okita kamegorookita kamegoro

インストールまわり

Gatsbyのインストール

$ gatsby new gatsby-typescript
$ rm -rf node_modules package.lock.json
$ yarn # パッケージマネージャーをnpmからyarnに変更
  1. ここでは何もオプションを加えずにそのままのGatsbyをインストール。
  2. yarnでパッケージ管理したいので、一度node_modulesとpackage.lock.jsonは削除
  3. yarnでインストール

Typescriptのインストール

# typescriptをプロジェクトへ導入
yarn add typescript -D
# typescriptの設定ファイルを生成
npx tsc --init
# message TS6071: Successfully created a tsconfig.json file.

上記コマンドの解説

yarn add typescript -D

これで、typescriptのnpmパッケージをインストールできる。
typescriptはTypeScriptを使うためのパッケージで、tscというTypescriptのコンパイラなどが内包されている。多分 TypeScirpit Compilerの略?

npx tsc -v
# Version 4.3.5
npx tsc --init

このtscを使って、初期化を実行すると、Typescriptの設定ファイルtsconfig.jsonが生成されて、TypeScriptのコンパイル環境がとりあえず完成する。

okita kamegorookita kamegoro

tsconfig.jsonの編集

tsconfig.jsonの設定については以下を参考するといいかも

https://qiita.com/ryokkkke/items/390647a7c26933940470#jsx

デフォルトでは以下が有効になっている(コメントアウトは削除している)。

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */
    /* Basic Options */
    "target": "es5",                                /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
    "module": "commonjs",                           /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    /* Strict Type-Checking Options */
    "strict": true,                                 /* Enable all strict type-checking options. */
    "esModuleInterop": true,                        /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    "skipLibCheck": true,                           /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true        /* Disallow inconsistently-cased references to the same file. */
  }
}

まだ正しい設定はわからないけど必要そうな設定を記述していく

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Basic Options */
    // "incremental": true,                         /* Enable incremental compilation */
    "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
    "lib": [
      "dom",
      "es2017"
    ] /* Specify library files to be included in the compilation. */,
    // "allowJs": true,                             /* Allow javascript files to be compiled. */
    // "checkJs": true,                             /* Report errors in .js files. */
    "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */,
    // "declaration": true,                         /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                      /* Generates a sourcemap for each corresponding '.d.ts' file. */
    // "sourceMap": true,                           /* Generates corresponding '.map' file. */
    // "outFile": "./",                             /* Concatenate and emit output to single file. */
    // "outDir": "./",                              /* Redirect output structure to the directory. */
    // "rootDir": "./",                             /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                           /* Enable project compilation */
    // "tsBuildInfoFile": "./",                     /* Specify file to store incremental compilation information */
    // "removeComments": true,                      /* Do not emit comments to output. */
    // "noEmit": true,                              /* Do not emit outputs. */
    // "importHelpers": true,                       /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,                  /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,                     /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true /* Enable all strict type-checking options. */,
    // "noImplicitAny": true,                       /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                    /* Enable strict null checks. */
    // "strictFunctionTypes": true,                 /* Enable strict checking of function types. */
    // "strictBindCallApply": true,                 /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,        /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                      /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                        /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                      /* Report errors on unused locals. */
    // "noUnusedParameters": true,                  /* Report errors on unused parameters. */
    // "noImplicitReturns": true,                   /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,          /* Report errors for fallthrough cases in switch statement. */
    // "noUncheckedIndexedAccess": true,            /* Include 'undefined' in index signature results */
    // "noImplicitOverride": true,                  /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
    // "noPropertyAccessFromIndexSignature": true,  /* Require undeclared properties from index signatures to use element accesses. */

    /* Module Resolution Options */
    // "moduleResolution": "node",                  /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                             /* Base directory to resolve non-absolute module names. */
    // "paths": {},                                 /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                              /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                             /* List of folders to include type definitions from. */
    // "types": [],                                 /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,        /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
    // "preserveSymlinks": true,                    /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,                /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                            /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                               /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,                     /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                       /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,              /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,               /* Enables experimental support for emitting type metadata for decorators. */

    /* Advanced Options */
    "skipLibCheck": true /* Skip type checking of declaration files. */,
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
  }
}

okita kamegorookita kamegoro

各パッケージの型定義インストール

「@types/」から始まる名前のパッケージとは - Qiita

一応、Gatsbyのサポートにより、依存関係として@types/react, @types/react-dom, @types/nodeなどはインストールされている模様であるけど、明示的に開発環境にaddしていてもいいのかな?と思ったりした

$ yarn add -D @types/node @types/react @types/react-dom @types/react-helmet @types/shopify-buy @types/styled-components

あとプラスして、デフォルトのスターターには gatsby-plugin-react-helmetが入っていて定義ファイルがないようなので、インストールする

$ yarn add -D @types/react-helmet

ちなみに、runtimeって必要なのか?ts-node, ts-node-devとか

okita kamegorookita kamegoro

GraphQL Schema, リクエストの型生成を出力するためのパッケージをインストール

GraphQLのレスポンスを型定義するためのプラグインは主に2つあるっぽい

  • gatsby-plugin-graphql-codegen
  • gatsby-plugin-typegen

gatsby-plugin-graphql-codegenはよく記事などで利用されているのを見かけるが、メンテナンスがされなくなっているので、gatsby-plugin-typegenを使うのを推奨している

gatsby-plugin-typegen | Gatsby

yarn add gatsby-plugin-typegen
okita kamegorookita kamegoro

GatsbyのTypescript対応

GatsbyはデフォルトでTypeScriptに対応するためのプラグイン[gatsby-plugin-typescript]をインクルードしている。
以下のリファレンスに沿って必要な設定があれば記述する。

gatsby-plugin-typescript | Gatsby

各コンポーネントのTypeScript化

Gatsbyはデフォルトでインストールした際に素のJavascriptで書かれている。
そのため拡張子.js, .jsx.ts, .tsxに書き換える。

pages以下のファイルについては、単純に.tsxに変換すれば良さそう。
components以下のファイルについては、少しprops周りを修正する必要あり
この辺のpurops周りが全然わからなかったので苦労した

ちなみに、typescriptに対応するテンプレートとして、gatsbyjs/gatsby-starter-default(デフォルトの)テンプレートには pages/using-typescript.tsxというのがある。

以下の変更が済んだら、ターミナルでyarn buildyarn developを実行して、ちゃんとビルドできることを確認する。

src/components

36行目から42行目までの Header.propTypesHeader.defaultPropsのオブジェクトはJSのReactでpropsに正常な値が渡っているか型・値のチェックをする機能らしい。

TypeScriptでは、引数や変数定義で型定義する必要があるため、propTypesがあると二重でチェックすることになり実質不要なはず。
一方で、typescriptでもあえてpropTypesを書くことはできる。その場合は、パッケージをインストールして利用する。

yarn add @types/prop-types

参考

src/components/layout.tsx
 /**
  * Layout component that queries for data
  * with Gatsby's useStaticQuery component
  *
  * See: https://www.gatsbyjs.com/docs/use-static-query/
  */

 import { graphql, useStaticQuery } from "gatsby"
-import PropTypes from "prop-types"
-import * as React from "react"
+import React, { ReactNode } from "react"
 import Header from "./header"
 import "./layout.css"

-const Layout = ({ children }) => {
+interface LayoutProps {
+  children: ReactNode
+}
+
+const Layout = ({ children }: LayoutProps) => {
   const data = useStaticQuery(graphql`
     query SiteTitleQuery {
       site {
         siteMetadata {
           title
         }
       }
     }
   `)

   return (
     <>
       <Header siteTitle={data.site.siteMetadata?.title || `Title`} />
       <div
         style={{
           margin: `0 auto`,
           maxWidth: 960,
           padding: `0 1.0875rem 1.45rem`,
         }}
       >
         <main>{children}</main>
         <footer
           style={{
             marginTop: `2rem`,
           }}
         >
           © {new Date().getFullYear()}, Built with
           {` `}
           <a href="https://www.gatsbyjs.com">Gatsby</a>
         </footer>
       </div>
     </>
   )
 }

-Layout.propTypes = {
-  children: PropTypes.node.isRequired,
-}
-
 export default Layout
src/components/header.tsx
 import { Link } from "gatsby"
-import PropTypes from "prop-types"
 import * as React from "react"

-const Header = ({ siteTitle }) => (
+interface HeaderProps {
+  siteTitle: string
+}
+
+const Header: React.FC<HeaderProps> = ({ siteTitle }) => (
   <header
     style={{
       background: `rebeccapurple`,
       marginBottom: `1.45rem`,
     }}
   >
     <div
       style={{
         margin: `0 auto`,
         maxWidth: 960,
         padding: `1.45rem 1.0875rem`,
       }}
     >
       <h1 style={{ margin: 0 }}>
         <Link
           to="/"
           style={{
             color: `white`,
             textDecoration: `none`,
           }}
         >
           {siteTitle}
         </Link>
       </h1>
     </div>
   </header>
 )

-Header.propTypes = {
-  siteTitle: PropTypes.string,
-}
-
-Header.defaultProps = {
-  siteTitle: ``,
-}
-
 export default Header
src/components/seo.tsx
 /**
  * SEO component that queries for data with
  *  Gatsby's useStaticQuery React hook
  *
  * See: https://www.gatsbyjs.com/docs/use-static-query/
  */

-import * as React from "react"
+import { graphql, useStaticQuery } from "gatsby"
 import PropTypes from "prop-types"
+import * as React from "react"
 import { Helmet } from "react-helmet"
-import { useStaticQuery, graphql } from "gatsby"

-function Seo({ description, lang, meta, title }) {
+interface SeoProps {
+  description: string
+  language: string
+  meta: []
+}
+
+Seo.propTypes = {
+  description: PropTypes.string,
+  lang: PropTypes.string,
+  meta: PropTypes.arrayOf(PropTypes.object),
+  title: PropTypes.string.isRequired,
+}
+
+function Seo({ description = "", lang = "ja", meta = [], title = "" }) {
   const { site } = useStaticQuery(
     graphql`
       query {
         site {
           siteMetadata {
             title
             description
             author
           }
         }
       }
     `
   )

   const metaDescription = description || site.siteMetadata.description
   const defaultTitle = site.siteMetadata?.title

   return (
     <Helmet
       htmlAttributes={{
         lang,
       }}
       title={title}
-      titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : null}
+      titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : undefined}
       meta={[
         {
           name: `description`,
           content: metaDescription,
         },
         {
           property: `og:title`,
           content: title,
         },
         {
           property: `og:description`,
           content: metaDescription,
         },
         {
           property: `og:type`,
           content: `website`,
         },
         {
           name: `twitter:card`,
           content: `summary`,
         },
         {
           name: `twitter:creator`,
           content: site.siteMetadata?.author || ``,
         },
         {
           name: `twitter:title`,
           content: title,
         },
         {
           name: `twitter:description`,
           content: metaDescription,
         },
       ].concat(meta)}
     />
   )
 }

-Seo.defaultProps = {
-  lang: `en`,
-  meta: [],
-  description: ``,
-}
-
-Seo.propTypes = {
             title
             description
             author
           }
         }
       }
     `
   )

         }
       }
     `
   )

   const metaDescription = description || site.siteMetadata.description
   const defaultTitle = site.siteMetadata?.title

   return (
     <Helmet
       htmlAttributes={{
         lang,
       }}
       title={title}
-      titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : null}
+      titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : undefined}
       meta={[
         {
           name: `description`,
           content: metaDescription,
         },
         {
           property: `og:title`,
           content: title,
         },
         {
           property: `og:description`,
           content: metaDescription,
         },
         {
           property: `og:type`,
           content: `website`,
         },
         {
           name: `twitter:card`,
           content: `summary`,
         },
         {
           name: `twitter:creator`,
           content: site.siteMetadata?.author || ``,
         },
         {
           name: `twitter:title`,
           content: title,
         },
         {
           name: `twitter:description`,
           content: metaDescription,
         },
       ].concat(meta)}
     />
   )
 }

-Seo.defaultProps = {
-  lang: `en`,
-  meta: [],
-  description: ``,
-}
-
-Seo.propTypes = {
-  description: PropTypes.string,
-  lang: PropTypes.string,
-  meta: PropTypes.arrayOf(PropTypes.object),
-  title: PropTypes.string.isRequired,
-}
-
 export default Seo

okita kamegorookita kamegoro

ESLintやPritterの対応

package.jsonを見たらESLintが入っていなかったので、インストールする

$ yarn add -D eslint eslint-config-prettier
$ touch eslintrc.js
eslintrc.js
module.exports = {
  parser: `@typescript-eslint/parser`,
  extends: [
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint",
  ],
  plugins: ["@typescript-eslint", "prettier"],
  parserOptions: {
    ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
    sourceType: "module", // Allows for the use of imports
  },
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  rules: {
    quotes: "off",
    "@typescript-eslint/quotes": [
      2,
      "backtick",
      {
        avoidEscape: true,
      },
    ],
    indent: ["error", 2, { SwitchCase: 1 }],
    "prettier/prettier": [
      "error",
      {
        trailingComma: "es5",
        semi: false,
        singleQuote: false,
        printWidth: 120,
      },
    ],
  },
}

okita kamegorookita kamegoro

gatsby-shopify-starterの内容を移植してく

最初に紹介したShopify用のテンプレートgatsby-shopify-starterを参考に、Shopify用の雛形を作っていく。ここまで大変長かった。。。。
他にもShopify向けのスターターはあるので、もう少し探して検討してみてもいいかもしれない。

gatsby-shopify-starterのpackage.jsonから入っていない必要そうなパッケージをインストール

$ yarn add shopify-buy styled-components styled-reset react-icons gatsby-source-shopify gatsby-plugin-google-fonts gatsby-optional-chaining babel-plugin-styled-components
  • shopify-buy
  • styled-components
  • styled-reset
  • react-icons
  • gatsby-source-shopify
  • gatsby-plugin-google-fonts
  • gatsby-optional-chaining
  • babel-plugin-styled-components

gatsby-imageなども含まれていたが、非推奨のパッケージとなっているので、gatsby-plugin-imageに変更しておく

okita kamegorookita kamegoro

gatsby-shopify-starterの内容を移植してく

最初に紹介したShopify用のテンプレートgatsby-shopify-starterを参考に、Shopify用の雛形を作っていく。ここまで大変長かった。。。。
他にもShopify向けのスターターはあるので、もう少し探して検討してみてもいいかもしれない。

gatsby-shopify-starterのpackage.jsonから入っていない必要そうなパッケージをインストール

$ yarn add shopify-buy styled-components styled-reset react-icons gatsby-source-shopify gatsby-plugin-google-fonts gatsby-optional-chaining gatsby-plugin-styled-components
  • shopify-buy
  • styled-components
  • styled-reset
  • react-icons
  • gatsby-source-shopify
  • gatsby-plugin-google-fonts
  • gatsby-optional-chaining

gatsby-imageなども含まれていたが、非推奨のパッケージとなっているので、gatsby-plugin-imageに変更しておく

okita kamegorookita kamegoro

gatsby-*.js 系を変更していく

これって ts に変更する方がいいのかな?

gatsby-config.js

gatsby-config.js
+require("dotenv").config({ //shopify の StorefrontAPIを .env ファイルで管理する
+  path: ".env",
+})
+
 module.exports = {
   siteMetadata: {
-    title: `Gatsby Default Starter`,
-    description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
-    author: `@gatsbyjs`,
+    // サイトのメタデータを記述していい
+    title: `gatsby + typescript + shopify`,
+    description: `Shopify Storefront build by gatsby and typescript`,
     siteUrl: `https://gatsbystarterdefaultsource.gatsbyjs.io/`,
   },
   plugins: [
     `gatsby-plugin-react-helmet`,
     `gatsby-plugin-image`,
     `gatsby-plugin-typegen`,
+    "gatsby-plugin-styled-components",
+    "gatsby-optional-chaining",
+    {
+      resolve: "gatsby-source-shopify",
+      options: {
+        shopName: process.env.GATSBY_SHOP_NAME,
+        accessToken: process.env.GATSBY_ACCESS_TOKEN,
+        apiVersion: "2021-07",
+      },
+    },
+    {
+      resolve: "gatsby-plugin-google-fonts",
+      options: {
+        fonts: [
+          /* eslint-disable no-useless-escape */ // eslintのエラーが解消できなかったため
+          "open sans:400",
+          "open sans:400i",
+          "open sans:700",
+          "open sans:700i",
+          "open sans:800",
+          "open sans:800i",
+          /* eslint-enable no-useless-escape */
+        ],
+      },
+    },
     {
       resolve: `gatsby-source-filesystem`,
       options: {
         name: `images`,
         path: `${__dirname}/src/images`,
       },
     },
     `gatsby-transformer-sharp`,
     `gatsby-plugin-sharp`,
     {
       resolve: `gatsby-plugin-manifest`,
       options: {
         name: `gatsby-starter-default`,
         short_name: `starter`,
         start_url: `/`,
         background_color: `#663399`,
         theme_color: `#663399`,
         display: `minimal-ui`,
         icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
       },
     },
     `gatsby-plugin-gatsby-cloud`,
     // this (optional) plugin enables Progressive Web App + Offline functionality
     // To learn more, visit: https://gatsby.dev/offline
     // `gatsby-plugin-offline`,
   ],
 }


okita kamegorookita kamegoro

tsconfig.jsonの修正

gatsbyのインポートで以下のようなエラーが生じていた

モジュール 'gatsby' が見つかりません。'moduleResolution' オプションを 'node' に....

これは tsconfig.json"module": "esnext"とした時に生じる。

ref
https://github.com/gatsbyjs/gatsby/issues/25324

解決方法としては tsconfig.json"moduleResolution": "node"とすればいい。

okita kamegorookita kamegoro

そういえば Typescriptの型チェック用のnpmスクリプトを編集する。

これで yarn typecheckで型チェック, yarn buildで型チェックしてからビルドが通るようになる

package.json
"scripts" {
   "build": "npm run type-check && gatsby build",
   "typecheck": "tsc --noEmit",
}
okita kamegorookita kamegoro

gatsby-source-shopify の変更

今回参考にしたスターター(https://github.com/tomphill/gatsby-shopify-starter)は、使用しているパッケージ類がちょいちょい古かったり、非推奨の物があるので注意が必要。

スターターは v3.8.0 だが、2021/08現在では 5.5.0となっており、結構破壊的な変更も行われている。

READMEを見ると プラグインの設定が変わっていることがわかる。

旧バージョン
https://github.com/gatsbyjs/gatsby/blob/b08c4c7358aa5a8119f33fadf6cd11aa59a44326/packages/gatsby-source-shopify/README.md

新バージョン
https://github.com/gatsbyjs/gatsby/tree/41f0ce7ad5010b94c635ad9a08a7f4c4ba564df7/packages/gatsby-source-shopify

gatsby-config.js
{
      resolve: "gatsby-source-shopify",
      options: {
-        shopName: process.env.GATSBY_SHOP_NAME,
-        accessToken: process.env.GATSBY_ACCESS_TOKEN,
+        password: process.env.SHOPIFY_ADMIN_PASSWORD,
+        storeUrl: process.env.SHOPIFY_STORE_URL,
        apiVersion: "2021-07",
      },
},

さらに、gatsby build, gatsby developを実行するとqueryエラーで落ちる。

以前までは、Shopify Storefront API を使ってビルドできるようだったが、最近追加されたShopify Admin APIの bulk operationsを使ってビルドを通しているよう。

https://shopify.dev/api/usage/bulk-operations/queries

なのでプライベートアプリにShopify Admin APIのパーミッションを与えないといけない。

For the Private app name enter Gatsby (the name does not really matter). Add the following under the Active Permissions for this App section:

Read access for Products
Read access for Product listings if you want to use Shopify's Product Collections in your Gatsby site
Read access for Orders if you want to use order information in your Gatsby site
Read access for Inventory and Locations if you want to use location information in your Gatsby site

gatsby-source-shopify のREADME通りに 商品管理と商品リストだけ追加すればとりあえず動きそう。

ところが、以下のようなエラーが生じる

ここで、 在庫(Inventory) のパーミッションを与えると無事ビルドに成功した。
原因はわからない。githubで聞いてもいいのかもしれない?

okita kamegorookita kamegoro

Gasbyの画像の扱いについて

Gatsbyの画像最適化について

コンポーネントでの扱い
=> StaticImageGatsbyImageがありそれぞれ使い分ける必要がある
使い分けについては以下のドキュメントを参考にすればよし。
https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-plugin-image/

また、gatsby-source-shopifyでは画像の取り扱いについても変更が起きている
以下に従って、gatsby-conifg.jsのプラグインにdownloadImages:trueを設定することで、Graphqlで shop.localFile.childImageSharp....が利用できるようになる
https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-shopify#download-images-up-front

gatsby-plugin-imageについても破壊的な変更があるのでマイグレーションが必要

https://www.gatsbyjs.com/docs/reference/release-notes/image-migration-guide/

okita kamegorookita kamegoro

ビルド時に同様のエラーがでた。

https://qiita.com/akitkat/items/82504eab3623d4441326

node --trace-deprecation node_modules/gatsby/dist/bin/gatsby develop

上記の記事と同様に npmパッケージのgotが原因のようであるが、正直最新版をインストールしてわざわざpackage.jsonに追加するのも微妙。依存関係のパッケージ側で解決されるのをまっても良さそう。

info Creating nodes from bulk operation PRODUCTS

 ERROR

(node:76144) [DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated
    at module.exports (/Users/abekeishi/ghq/github.com/TanisukeGoro/chefrepi-storefront/node_modules/timed-out/index.js:9:17)
    at EventEmitter.<anonymous> (/Users/abekeishi/ghq/github.com/TanisukeGoro/chefrepi-storefront/node_modules/got/index.js:244:5)
    at Object.onceWrapper (node:events:514:26)
    at EventEmitter.emit (node:events:394:28)
    at makeRequest (/Users/abekeishi/ghq/github.com/TanisukeGoro/chefrepi-storefront/node_modules/cacheable-request/src/index.js:94:9)
    at /Users/abekeishi/ghq/github.com/TanisukeGoro/chefrepi-storefront/node_modules/cacheable-request/src/index.js:104:14
    at runNextTicks (node:internal/process/task_queues:61:5)
    at processImmediate (node:internal/timers:437:9)


okita kamegorookita kamegoro

Gatsbyの公式ドキュメントを見ると、ページの生成に利用するGraphQLに変数を渡したい場合、contextを付与すればいいらしい。

https://www.gatsbyjs.com/docs/creating-and-modifying-pages#pass-context-to-pages

実際に書くとこうなる。

gatsby-node.js
  data.allShopifyProduct.edges.forEach(({ node }) => {
    createPage({
      path: `products/${node.handle}`,
      context: {
        hodehoge: 'testo',
        storefrontId: node.storefrontId,
      },
      component: path.resolve('./src/template/ProductTemplate/index.tsx'),
    })
  })
src/template/ProductTemplate/index.tsx
// 中略
export const query = graphql`
  query ProductQuery($shopifyId: String) {
    shopifyProduct(shopifyId: { eq: $shopifyId }) {
      ...ShopifyProductFields
    }
  }
// 中略

この時、contextのkeyがGraphQLの変数に自動的に読み替えられて実行できるため変数として渡すことができるようなのであるが、特にGraphQL側の変数に関係のないパラメータのみを付与したとしてもなぜがビルドできてしまう問題にぶち当たった。まさに上記のように。

原因も解決方法もまだわかっていない。

ちなみにビルドはできるが、データは1件しか取得できていないみたいで、どのページを見ても同じデータがpropsで与えられている。
contextとGraphQLのパラメータを揃えるとちゃんとビルドされて適切なpropsが渡るようになる。