🛡

Gatsby.jsのTypeScript化 2020

2020/10/04に公開

個人で Gatsby.js を使い始めたのですが、検索して見つかる Gatsby.js の TypeScript 化についての記事の情報が少し古かったので、自分で書き直してみました。
順を追ってスターターパッケージを TypeScript 化していきます。

なお今回のコードは全て下記リポジトリにあります。
もし、何かエラーになった際は参照してみてください。

https://github.com/kawamataryo/gatsby-typescript-sample

この記事は以下のバージョン時点の情報です。

  • Gatsby.js: 2.24.66
  • gatsby-plugin-typegen: 2.2.1

0. プロジェクトの作成

gatsby コマンドを使うめにgatsby-cliの追加。

npm install -g gatsby-cli

一番標準の blog のスターターでプロジェクトを作成します。

gatsby new gatsby-typscript-sample https://github.com/gatsbyjs/gatsby-starter-blog

ここからこのプロジェクトを TypeScript 化していきます。

1. tsconfig.jsonの追加

最初に、tsconfig.jsonの追加とtypescriptのインストールをします。

npx tsc --init
yarn add typescript -D

tsconfig.jsonは以下のようにして設定します。

{
  "include": ["./src/**/*"],
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "lib": ["dom", "es2017"],
    "jsx": "react",
    "strict": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "noEmit": true,
    "skipLibCheck": true,
    "moduleResolution": "node"
  }
}

そして、packege.jsonに TypeScript の型チェック用の npm スクリプトを追加します。
すでにtsconfig.json側でnoEmit: trueを設定しているのでtscコマンドで JS コードは生成されません。

{
  // ...
  "scripts": {
    // ...
    "typecheck": "tsc",
  }
}

これで以降、yarn typecheckで TS の型チェックを行えるようになります。

2. 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'>> }
    )> } };
//...

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

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

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

各コンポーネントのファイル拡張子を.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から型を取得できます。

🔗 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 化も完了です🎉

終わりに

以上、「Gatsby.js の TypeScript 化 2020」でした。
まだまだ Gatsby.js も React も使い始めたばかりなので、もっと良いやり方があれば気軽にコメントを頂けると嬉しいです!

参考

Discussion