華麗なるGatsby.jsの実践(型を変更してみよう)

# GraphQLスキーマをカスタマイズする

先日、markdownのimgをgraphQLで管理しようとしたところ、型の類推エラーによってgraphQLのスキーマをいじらなければならなくなりました。なのでこのガイドを学習します!
ハイパー意訳がほぼほぼですのでご了承ください🙇‍♂️

##graphQLのスキーマ言語をおおよそ知る必要がある

その前に、そもそも、graphQLに関する記述法を知らなかったので、そこをざっくり学習します。
https://employment.en-japan.com/engineerhub/entry/2018/12/26/103000
私は上記で学習させていただきました!

特に必要となる知識は、スキーマ言語(Web APIの仕様を定義する方)の
fieldの定義方法と,Interfaceについてです。

```
type Query {
 currentUser: User!
}
```

```
interface Node {
 id: ID!
}
```

Interfaceは必要となるfieldのセットを束ねた抽象型です。


## 公式ガイド
では[公式ガイド](https://www.gatsbyjs.org/docs/schema-customization/#extensions-and-directives)を見てみます。

このガイドは

- プラグインの作成者

- 自動型類推によって作成されたスキーマを修正したい方（私)

- ビルドの最適化を測る方

- スキーマに興味のある方

を対象としているようです。こちらかなり長いのですが、ここでは前編部分のみを扱います。
気になる方は続きを読むと良さそうです!

graphQLの強みは何と言っても多くの情報源を一括で扱えること!
そのためにはGraphQLスキーマを生成しなければなりません。



### 例 マークダウンとJSON
Markdownファイルからブログ記事を生成する。記事内容に加えて筆者情報をJSON形式で提供する。
時々ある寄稿者の情報は別のJSONとして保存する。


```md:.md
---
title: Sample Post
publishedAt: 2019-04-01
author: jane@example.com
tags:
  - wow
---
# Heading
Text
```



```json:author.json
[
  {
    "name": "Doe",
    "firstName": "Jane",
    "email": "jane@example.com",
    "joinedAt": "2018-01-01"
  }
]
```


```json:contributor.json
[
  {
    "name": "Doe",
    "firstName": "Zoe",
    "email": "zoe@example.com",
    "receivedSwag": true
  }
]
```

これらをGraphQLでクエリするためには`gatsby-source-filesystem,gatsby-transformer-remark,gatsby-transformer-json`を使用しますが、これらのプラグイン内部で何が行われているかというと、
**markdownファイルをユニークなidとMarkdownRemarkタイプを持つnodeオブジェクトに変換**しています。

同様に著者データはAuthorJsonタイプのnodeオブジェクトに変換され、
寄稿者データはContributorJson型のノードオブジェクトに変換されています。

<img width="678" alt="スクリーンショット 2020-05-18 18.57.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441432/395e44ef-be1b-07db-9c14-1a74b949b4f1.png">



## Node interface

Source pluginやtransformer pluginによって作られるGatsbyのGraphQLのスキーマです。
Id, parent,children,またinternal型などといったfieldがセットされています。

```
interface Node {
  id: ID!
  parent: Node!
  children: [Node!]!
  internal: Internal!
}

type Internal {
  type: String!
}
```

** TODO: internalって何でしょう..イマイチ調べてもわからずでした。

###例 autor.json
例)Gatsby-transformer-jsonで作成するautor.json用のnode型は、以下のようになっています。

```
type AuthorJson implements Node {
  id: ID!
  parent: Node!
  children: [Node!]!
  internal: Internal!
  name: String
  firstName: String
  email: String
  joinedAt: Date
}
```

実際にgatsbyが作成したスキーマを確認するには、`GraphQL Playground`がオススメです。

プロジェクト上で

```
$ GATSBY_GRAPHQL_IDE=playground gatsby develop
```

そして`http://localhost:8000/___graphql`を開いて右画面側にある Schemeタブを開きます。
![スクリーンショット 2020-05-18 18.03.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441432/a930bef2-3ce2-0ba0-06da-44417da21d37.png)



思った以上にぎっしりしてます。



## 自動型推論

先ほどのautor.jsonを見てみましょう。

```
[
  {
    "name": "Doe",
    "firstName": "Jane",
    "email": "jane@example.com",
    "joinedAt": "2018-01-01"
  }
]
```

どこにも型定義などされていないですが、GraphQLではうまいこと動かせています。
これは型の類推を行なってくれているからですね。
１つ１つのfield内容を確認し、型をチェックすることで実現しています。

しかしこの型類推には２つの問題点があります。

- 時間がかかり、負担がかかる

- 型とデータが異なっていた場合、型の類推が失敗する

  ２つ目についてですが、例をあげます。

  ```md:.md
  +  {
  +    "name": "Doe",
  +    "firstName": "John",
  +    "email": "john@example.com",
  +    "joinedAt": "201-04-02"
  +  }
  ]
  ```

  joinedAt部分がスペルミスによって、Dateと解釈できなくなっています。

  これらを解決するためには、型を明示的に示すことです。

  

  ## 型定義

  `createType  action`を使って型を明示的に定義します。

  ```react:gatsby-node.js
  exports.createSchemaCustomization = ({ actions }) => {
    const { createTypes } = actions
    const typeDefs = `
      type AuthorJson implements Node {
        joinedAt: Date
      }
    `
    createTypes(typeDefs)
  }
  ```

  全ての型を定義する必要がない点に注意してください。(name,firstName等)



そもそも型推論をやらない!というストイックな方もいるでしょう。
これを行うことでパフォーマンスの向上が見込めます。先ほども開設したように、型の類推は負担が大きいため、大規模なプロジェクトほどパフォーマンス低下が著しくなります。
`@dontInfer`ディレクティブを用いて型推論をオプトアウトできます。

*ディレクティブってなんだ？という方は先ほどのGraphQLスキーマについての解説をどうぞ!https://employment.en-japan.com/engineerhub/entry/2018/12/26/103000)*

```gatsby-node.js
exports.createSchemaCustomization = ({ actions }) => {
  const { createTypes } = actions
  const typeDefs = `
    type AuthorJson implements Node @dontInfer {
      name: String!
      firstName: String!
      email: String!
      joinedAt: Date
    }
  `
  createTypes(typeDefs)
}
```

これがストイックなバージョンになります。
Node interfaceが提供するフィールドについては、Gatsby側で追加してくれるので明示しなくて大丈夫です。(id,parentなど)



## ネストしている型

今まではString型やDate型などのスカラー型のみを扱ってきました。GraphQLでは他にも

- ID型

- Int型

- Float型

- Boolean型

- JSON型

  なども扱うことが可能です。また、複雑なオブジェクト値だって扱えます。

  markdown-remarkを例にとってみます。

  以下の記述によって`frontmatter.tags`は必ず文字の配列となります。

  ```gatsby-node.js
  exports.createSchemaCustomization = ({ actions }) => {
    const { createTypes } = actions
    const typeDefs = `
      type MarkdownRemark implements Node {
        frontmatter: Frontmatter
      }
      type Frontmatter {
        tags: [String!]!
      }
    `
    createTypes(typeDefs)
  }
  ```

こちらの記述のように、直接Frontmatter型を指定すると失敗してしまいます。

```gatsby-node.js
exports.createSchemaCustomization = ({ actions }) => {
  const { createTypes } = actions
  const typeDefs = `
    # これは失敗!!!
    type Frontmatter {
      tags: [String]!
    }
  `
  createTypes(typeDefs)
}
```


なぜなら、Frontmatter型がソースプラグインやトランフォーマープラグインによって作成されないため、Nodeインターフェースが実装されくなってしまうからです。

そのためにこのような処理となります。