Gatsby.jsTypeScript 化する

tsconfig.jsonを追加

tsconfig.json

 

GraphQL Schema, リクエストの型生成

Gatsby はリソースに対して GraphQL でリクエストを送りデータを取得する GraphQL リクエストのレスポンスの型を、gatsby-plugin-typegen を使い生成する。

yarn add gatsby-plugin-typegen

gatsby-config.jsの plugins にgatsby-plugin-typegenを追記する。

src/components/index.ts

module.exports = {
  siteMetadata: {
    // ...
  },
  plugins: [
    // ...
    `gatsby-plugin-typegen`
  ],
}

次に、各コンポーネントの query にクエリ名を追加していきます。
この変更をすることでそのクエリ専用の型が生成されます。

(例: src/components/index.js query BlogIndexの部分を追記している)

🔗 src/components/index.ts

//...
export const pageQuery = graphql`
  query BlogIndex {
    site {
      siteMetadata {
        title
      }
    }
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
      nodes {
        excerpt
        fields {
          slug
        }
        frontmatter {
          date(formatString: "MMMM DD, YYYY")
          title
          description
        }
      }
    }
  }
`

最後にyarn buildを実行すると、src/__generated__/gatsby-types.tsが生成されているはずです。
ここに GraphQL リクエストの型定義があります。
先ほど追加した BlogIndex クエリの型を見てみると、、

🔗 src/pages/index.ts

//...
type BlogIndexQueryVariables = Exact<{ [key: string]: never; }>;
 
 
type BlogIndexQuery = { readonly site: Maybe<{ readonly siteMetadata: Maybe<Pick<SiteSiteMetadata, 'title'>> }>, readonly allMarkdownRemark: { readonly nodes: ReadonlyArray<(
      Pick<MarkdownRemark, 'excerpt'>
      & { readonly fields: Maybe<Pick<Fields, 'slug'>>, readonly frontmatter: Maybe<Pick<Frontmatter, 'date' | 'title' | 'description'>> }
    )> } };
//...

ちゃんと生成されてますね! 最高便利。

各コンポーネントファイルのTypeScript化

これで準備ができたので、各ファイルを TypeScript 化していきます。
gatsby-plugin-typescriptの追加から入る記事が多いのですが、2020 年 10 月現在、Gatsby にはgatsby-plugin-typescriptがすでに組み込まれているので、何もせずで大丈夫です。

何か TypeScript のビルド関連で追加の設定をしたい場合は、gatsby-config.js の plugins でgatsby-plugin-typescriptを追加して、option を設定してください。

各コンポーネントのファイル拡張子を.jsから.tsxに書き換えましょう。
そして、StaticQuery の戻り値など型エラーとなっている箇所に型をつけていきます。

例えば、src/pages/index.tsの型付けは以下のようになります。

🔗 src/pages/index.ts

import React from "react"
import { Link, graphql } from "gatsby"
import { PageProps } from "gatsby"
 
import Bio from "../components/bio"
import Layout from "../components/layout"
import SEO from "../components/seo"
 
const BlogIndex:React.FC<PageProps<GatsbyTypes.BlogIndexQuery>> = ({ data, location }) => {
  const siteTitle = data.site?.siteMetadata?.title || `Title`
  const posts = data.allMarkdownRemark.nodes
 
  // ... 以下略
}

ポイントは以下のようにReact.FCPagePropsなどのジェネリクス型を使うことと、gatsby-plugin-typegenで生成した型を使うことです。

const BlogIndex:React.FC<PageProps<GatsbyTypes.BlogIndexQuery>> = ({ data, location }) => { /* -- */ }

これでdataの型がBlogIndexQueryの型で推論されます。
あとは、適宜 Optional Chaining や、Non null Assertion を使って型エラーを解決しましょう。

4. gatsby-Node.jsのTypeScript化

gatsby-node.jsでも TypeScrip で書けるようにしていきます。ここではts-nodeを追加ます。

ここの書き方は@Takepepeさんの以下の記事を参考にさせていただきました。良記事ありがとうございます🙏
Gatsby.js を完全TypeScript化する - Qiita

yarn add -D ts-node

そして、gatsby-config.jsを以下のように変更します。

🔗 gatsby-config.js

"use strict"
 
require("ts-node").register({
  compilerOptions: {
    module: "commonjs",
    target: "esnext",
  },
})
 
require("./src/__generated__/gatsby-types")
 
const {
  createPages,
  onCreateNode,
  createSchemaCustomization,
} = require("./src/gatsby-node/index")
 
exports.createPages = createPages
exports.onCreateNode = onCreateNode
exports.createSchemaCustomization = createSchemaCustomization

そして、今までgatsby-node.jsに記述していた内容をsrc/gatsby-node/index.tsに移動して、型を設定します。
基本的に node の API はGatsbyNodeから型を取得できます。

本当は、allMarkdownRemarkのクエリ部分の方もgatsby-plugin-typegenで生成したかったのですが、上手く認識してくれませでした。やり方わかる方いたら教えてください🙏
(ドキュメントの Provides utility types for gatsby-node.js.はまだチェックがついていないので、まだ未対応なのかな?)。

🔗 src/gatsb-node/index.ts

import path from "path"
import { GatsbyNode, Actions } from "gatsby"
import { createFilePath } from "gatsby-source-filesystem"
 
export const createPages: GatsbyNode["createPages"] = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions
 
  const blogPost = path.resolve(`./src/templates/blog-post.js`)
 
  const result = await graphql<{ allMarkdownRemark: Pick<GatsbyTypes.Query["allMarkdownRemark"], 'nodes'> }>(
    `
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 1000
        ) {
          nodes {
            fields {
              slug
            }
            frontmatter {
              title
            }
          }
        }
      }
    `
  )
 
  //...
 
  }
}
 
export const onCreateNode: GatsbyNode["onCreateNode"] = ({ node, actions, getNode }) => {
  const { createNodeField } = actions
  //...
}
 
export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] = async ({ actions }: { actions: Actions}) => {
  const { createTypes } = actions
  // ...
}
 

これでgatsby-Node.jsの TypeScript 化も完了です🎉

参考