GatsbyのソースプラグインをTypeScriptで作る方法 〜Hello Worldするだけの30行程度のプラグインを例に〜

この投稿ではGatsbyソースプラグインをTypeScriptで作る方法を説明します。

## 今回作るもの

今回作るものとしては、単純なデータソースから、Gatsbyのノードを登録するプラグインを開発し、次のようなGraphQLで:

```graphql
query MyQuery {
  allHelloWorld {
    edges {
      node {
        messageId
        message
      }
    }
  }
}
```

次のようなクエリ結果を得られるようにすることとです:

```json
{
  "data": {
    "allHelloWorld": {
      "edges": [
        {
          "node": {
            "messageId": 1,
            "message": "Hello World!"
          }
        },
        {
          "node": {
            "messageId": 2,
            "message": "Good Morning World!"
          }
        },
        {
          "node": {
            "messageId": 3,
            "message": "Good Night World!"
          }
        }
      ]
    }
  }
}
```

なお、この投稿で作るプラグインの完成形はGitHubに置いてあります: [gatsby-playground/plugins/gatsby-source-hello-world at master · suin/gatsby-playground](https://github.com/suin/gatsby-playground/tree/master/plugins/gatsby-source-hello-world)

## ディレクトリ構成

Gatsbyプラグインを開発するには、ディレクトリ構成を次のようにします。

```
.
├── plugins
│   └── gatsby-source-hello-world → プラグインのプロジェクトルート
│       ├── src
│       │   └── gatsby-node.ts → プラグインのソース
│       ├── gatsby-node.js → コンパイル後のプラグイン
│       └── package.json
└── site → プラグインデバック用サイト
    ├── gatsby-node.js → サイト設定
    └── package.json
```

ポイントとしては、プラグインのパッケージと、Gatsbyサイトを別々のディレクトリにすることです。

* 開発するプラグインは、`plugins`ディレクトリに置く。
* プラグインをGatsbyと結合して、試すために、`site`ディレクトリにGatsbyをインストールしておく。

## デバッグ用サイトを作る

シンプルなGatsbyを入れて、デバッグ用サイトを作ります:

```
gatsby new site https://github.com/gatsbyjs/gatsby-starter-default.git
```

## プラグインのパッケージを作る

作るプラグインごとに1つのパッケージを作ります。なので、プラグインごとに専用のディレクトリと、`package.json`を作ることになります。

まず、ディレクトリ作り:

```
mkdir -p plugins/gatsby-source-hello-world
cd plugins/gatsby-source-hello-world
```

ちなみに、`gatsby-node.js`で指定するプラグインの名前はこのディレクトリ名になります。`package.json`の`name`フィールドではなく。

ここでいうと、`gatsby-source-hello-world`がプラグインの名前になります。

次に、`package.json`を作る:

```
yarn init
```

### プラグインをTypeScriptで開発するための準備

GatsbyプラグインをTypeScriptで開発したいので、その準備をしていきます。

まず、TypeScriptと必要な型定義ファイルをインストールしておきます:

```
yarn add -D typescript gatsby @types/react-dom
```

* gatsby: 型定義ファイルだけ欲しいのですが、型定義ファイル単体では配布してないようなので全部いれます。
* @types/react-dom: gatsbyが依存しているので必要です。

次に、`tsconfig.json`を作ります。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "ES2019",
    "module": "commonjs",
    "outDir": "./",
    "esModuleInterop": true,
    /* 以下コンパイル設定はお好みで */
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "inlineSourceMap": true,
    "inlineSources": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "src"
  ],
  "exclude": [
    "node_modules",
    "*.ts"
  ]
}
```

この設定で唯一大事なのは、`outDir`をプラグインパッケージのルートにすることです。コンパイル後のプラグインファイル`gatsby-node.js`がルートにないと、Gatsbyがプラグインとして認識してくれないためです。

## シンプルなソースプラグインを作る

ソースプラグインは、ファイルやAPIからデータを取ってきて、Gatsbyで扱えるようにするのが基本的な目的ですが、ここでは、話を単純にするために、APIなどを使わずにプラグインを作ってみます。

具体的には、下記のようなJavaScriptオブジェクトをGatsbyのGraphQLで参照できるようにしてみます。

```js
[
  { messageId: 1, message: `Hello World!` },
  { messageId: 2, message: `Good Morning World!` },
  { messageId: 3, message: `Good Night World!` },
]
```

ソースプラグインは、`src/gatsby-node.ts` に実装します。

下記がその内容です。`sourceNodes`を`export`すると、Gatsbyが起動時にこの関数を呼び出してきます。`sourceNodes`関数では、コンテンツごとに`createNode`を呼び出していって、Gatsbyにコンテンツを登録していきます。

```typescript:src/gatsby-node.ts
import { SourceNodesArgs } from 'gatsby'

// noinspection JSUnusedGlobalSymbols
export const sourceNodes = ({
  createNodeId,
  createContentDigest,
  actions: { createNode },
}: SourceNodesArgs): void => {
  const dataset: ReadonlyArray<Data> = [
    { messageId: 1, message: `Hello World!` },
    { messageId: 2, message: `Good Morning World!` },
    { messageId: 3, message: `Good Night World!` },
  ]

  dataset.forEach(data => {
    createNode({
      ...data,
      id: createNodeId(`HelloWorld${data.messageId}`),
      internal: {
        type: `HelloWorld`,
        contentDigest: createContentDigest(data),
      },
    })
  })
}

type Data = {
  readonly messageId: number
  readonly message: string
}
```

データソースは、このコードでは、`dataset`になります。今回は、説明をシンプルにするためにハードコーディングしたオブジェクトになっていますが、ここをAPIから取ってきたデータなどに置き換えれば、外部データをソースにするプラグインになります。

`id: createNodeId`の部分は、ユニークなIDになるようにします。

`type`の`HelloWorld`の部分は、GraphQLの`allHelloWorld`や`helloWorld`になる部分なので、大文字始まりのキャメルケースにしておきます。

`contentDigest`の`createContentDigest`関数は、ノードのチェックサムを計算するための関数で、`string`か`object`が渡せるので、今回は`Data`オブジェクトをそのまま渡しています。

## プラグインをJavaScriptにコンパイルする

TypeScriptで書いたコードは`tsc`コマンドでコンパイルします。

```
cd plugins/gatsby-source-hello-world
npx tsc
```

すると、`plugins/gatsby-source-hello-world/gatsby-node.js`が生成されます。これでプラグインの実装は完了となります。

## デバッグサイトで作ったプラグインを読み込ませる

最後に、デバッグサイトで作ったプラグインを読み込ませて、Gatsbyを起動し、GraphQLでソースを参照するところまでやってみましょう。

まず、デバッグ用サイトのパッケージに移動して、作ったプラグインをインストールします:

```
cd site
yarn add link:../plugins/gatsby-source-hello-world
```

この`yarn add link`は`node_modules`に`../plugins/gatsby-source-hello-world`へのシンボリックリンクを作成します。

* 参照: [Yarnでローカルのパッケージをaddする方法 - Qiita](https://qiita.com/suin/items/409dc019de605830b4c2)
* npmはこちら: [npmでローカルのパッケージをinstallする方法 - Qiita](https://qiita.com/suin/items/c9c342f557bd885dbe06)

次に、`gatsby-config.js`にプラグインを追加します:

```site/gatsby-config.js
module.exports = {
  siteMetadata: {
    // ...略
  },
  plugins: [
    `gatsby-source-hello-world`, // ←追加！
    // ...略
}
```

そうしたら、Gatsbyを起動します:

```
npx gatsby develop
```

しばらくすると、GraphiQLが起動するのでブラウザで開きます:

http://localhost:8000/___graphql

GraphiQL上で、次のクエリが実行できるか確認します:

```graphql
query MyQuery {
  allHelloWorld {
    edges {
      node {
        messageId
        message
      }
    }
  }
}
```

<img width="901" alt="Screenshot_2020_05_11_12_05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889/b8622252-b70f-ad55-e596-a38150550727.png">

結果に登録したノードが表示されていれば成功です。