[Gatsby] TypeScript化とWordPressの型

復習がてら公式のスターター(gatsby-starter-minimalgatsby-starter-wordpress-blog)をTypeScript化したメモ。

TypeScript化の手順

gatsby newコマンドで開始した初期構成から。(スターター無指定だとminimalになる)

WordPressのスターターを利用する場合は変更箇所が増えるけどやることはそれほど変わらない🤔

Eslint

yarn add -D eslint eslint-plugin-react eslint-plugin-graphql

.eslintrc.jsをルートに作成して設定を追加

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2021: true, 
  },
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  plugins: ['react', 'graphql'],
  extends: [
    'eslint:recommended',
    'plugin:react/recommended'
  ],
  settings: {
    react: {
      version: 'detect',
    },
  },
}

ESLintはECMAScript5構文を想定しているのでenvでes6以上をtrueしてないとconstで怒られたりする。parserOptionsでサポートする構文を指定できるが、この設定だけだとグローバルを有効にしないので envだけかenvparserOptionsの両方を使用する。

Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn’t recognize. We recommend using eslint-plugin-react if you are using React and want React semantics.

https://eslint.org/docs/user-guide/configuring/language-options

ということなのでReactのプラグインを追加する。

.eslintignoreを作成

node_modules/
public/
.cache/
**/__generated__/

prettier

prettierを追加。eslint用の設定プラグインも入れる。
WordPressスターターの場合prettierは含まれているのでプラグインだけ追加。

yarn add -D prettier eslint-config-prettier eslint-plugin-prettier

eslintrc.jsにprettierの設定を追加。

extends: [
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier',
    'plugin:prettier/recommended',
  ]

.prettierrcを作成(ルールはおこのみで)

{
  "arrowParens": "avoid",
  "useTabs": false,
  "tabWidth": 2,
  "semi": false,
  "singleQuote": true,
  "jsxSingleQuote": true
}

VSCode

.vscode/settings.jsonに設定を追加。再起動する。

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",

  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

gatsby-plugin-typegen

自動で型やスキーマを生成するプラグイン。

yarn add gatsby-plugin-typegen

gatsby-configに設定追加

{
    resolve: 'gatsby-plugin-typegen',
    options: {
      emitSchema: {
        'src/__generated__/gatsby-schema.graphql': true,
        'src/__generated__/gatsby-introspection.json': true,
      },
      emitPluginDocuments: {
        'src/__generated__/gatsby-plugin-documents.graphql': true,
      },
    },
  },

gatsbyを再起動するとsrcに_generated_以下が生成される。

eslintrc に追加

rules: {
    'graphql/template-strings': [
      'error',
      {
        env: 'relay',
        tagName: 'graphql',
        schemaJsonFilepath: path.resolve(
          __dirname,
          'src/__generated__/gatsby-introspection.json'
        ),
      },
    ],
  },

tsconfig.json

typegenを使う場合は作成必要。

{
  "include": [
    "./src/**/*",
    "./config/*.ts" ],
  "exclude": ["node_modules", "public", ".cache"],
  "compilerOptions": {
    "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
    "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
    "lib": [
      "dom",
      "esnext"
    ],
    "jsx": "react-jsx" /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */,
    "noEmit": true /* Do not emit outputs. */,
    "strict": true /* Enable all strict type-checking options. */,
    "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
    "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. */
    "baseUrl": "./",
    "typeRoots": [
      "src/@types",
      "node_modules/@types",
      "src/__generated__" /*gatsby-plugin-typegen */,
    ],
    "plugins": [
      {
        "name": "ts-graphql-plugin",
        "schema": "src/__generated__/gatsby-schema.graphql",
        "tag": "graphql"
      }
    ],
  }
}

TypeScriptとESLintのTypeScriptプラグインをインストールする。

yarn add -D typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin

.eslintrcに設定を追加。JSとTSが混在している場合はoverridesで設定しないとJSがTS扱いされて無限にエラーを吐く。

  overrides: [
    {
      files: ['*.ts', '*.tsx'],
      parser: '@typescript-eslint/parser',
      parserOptions: {
        tsconfigRootDir: __dirname,
        ecmaFeatures: {
          jsx: true,
        },
        project: ['./tsconfig.json'],
      },
      extends: ['plugin:@typescript-eslint/recommended'],
      plugins: ['@typescript-eslint'],
    },
  ],

src以下に.eslintを新たに配置して { root: true } にした上で TypeScript 向けの設定を書いてもいい。

apollo.config.js

graphqlのsuggestとかが有効になる

module.exports = {
  client: {
    name: 'your-project-name',
    tagName: 'graphql',
    includes: [
      './src/**/*.{ts,tsx}',
      // './src/__generated__/gatsby-plugin-documents.graphql'
    ],
    service: {
      name: 'GatsbyJS',
      localSchemaFile: './src/__generated__/gatsby-schema.graphql'
    }
  }
}

Gatsby設定ファイルのTypeScript化

ルートに存在するgatsby-config.js、gatsby-node.js などをts化する。
gatsby-plugin-ts-config プラグインが存在するので、npmかyarnでインストールしておく。

yarn add --dev gatsby-plugin-ts-config

config/gatsby-config.ts ファイルを作成する。

import { GatsbyConfig, PluginRef } from 'gatsby'

const siteMetadata = {}

const plugins: PluginRef[] = []

export default { siteMetadata, plugins } as GatsbyConfig

siteMetadataとpluginsの中にgatsby-config.jsの中身を移植する。
__dirnamepath.resolve とかに変更しておく。

代わりにgatsby-config.jsにはこのプラグインの設定を書く。(V2で書き方が変わった)

const { useGatsbyConfig } = require("gatsby-plugin-ts-config");

module.exports = useGatsbyConfig(() => require("./config/gatsby-config"));

gatsby-node.jsも同様に変更

const { useGatsbyNode } = require("gatsby-plugin-ts-config");

module.exports = useGatsbyNode(() => require("./config/gatsby-node"), {});

pages/index.jsのTypeScript化

拡張子をTSXに変更してからTypeScriptに合わせてソースを修正。
拡張子を変更した時点でTypeScriptのエラーが出なかったら何かしら内部エラーが起きてるので、エディタのコンソールを見て修正する。

関数型コンポーネントの型

import { PageProps } from 'gatsby'

const IndexPage: React.FC<PageProps> = ( props: PageProps ): JSX.Element => {
// ... 省略
}

CSSの型

const linkStyle: React.CSSProperties = {
  color: '#8954A8',
  fontWeight: 'bold',
  fontSize: 16,
  verticalAlign: '5%',
}
// <a style={linkStyle}>

404.jsもtsx化したらスターターのTS化は完了

WordPressの型

gatsby-source-wordpress によってWordpressのデータを利用するGatsbyの場合、Wordpressの型を指定しなければならない。起動する度にgatsby-plugin-typegenが自動的に型定義を生成してくれるので、大抵は src/__generated__/gatsby-types.ts を見れば必要な型を見つけることができる。

以下は gatsby-starter-wordpress のソースを元にしている。

blog-post-archive.tsx

dataの型はGatsbyTypes.Queryを指定する。

import { Link, graphql, PageProps } from 'gatsby'
// ...

type PageContext = {
  nextPagePath: string
  previousPagePath: string
}

const BlogIndex: React.FC<PageProps<GatsbyTypes.Query, PageContext>> = ({
  data,
  pageContext: { nextPagePath, previousPagePath },
}: PageProps<GatsbyTypes.Query, PageContext>): JSX.Element => {
   const posts = data.allWpPost.nodes
// ...

parseとかLinkのto属性でエラーが出る。

Argument of type 'Maybe<string>' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

GatsbyTypes.Maybytype Maybe<T> = T | undefined なので undefinedを許さない関数やプロパティにはこのエラーで怒られ続ける事になる。

const title = post.title as string
const uri = post.uri as string
const excerpt = post.excerpt as string

// または
to={post.uri || '/'}
parse(post.title || 'Untitled')
parse(post.excerpt || '')

いちいち変更するの面倒なんだけど、なんかいい方法ないのかな?

blog-post.tsx

dataの中にprevious, next, postがあるのでそれぞれ型が必要

const BlogPostTemplate = ({
  data: { previous, next, post },
}: PageProps<{
  previous: GatsbyTypes.WpPost
  next: GatsbyTypes.WpPost
  post: GatsbyTypes.WpPost
}>): JSX.Element => {

Maybeで怒られるのはarchiveと同じ。

components/bio.tsx

戻り値の型指定だけでエラーは消える。

const Bio = (): JSX.Element => {

components/layout.tsx

childrenは何を渡すのかによって型が変わるが、大抵の場合は React.ReactNode で許される。

const Layout = ({
  isHomePage,
  children,
}: {
  isHomePage?: boolean
  children?: React.ReactNode
}): JSX.Element => {

components/seo.tsx

プロパティとして渡すものの型を指定しておく。metaがちょっとややこしい

const Seo = ({
  description,
  lang,
  meta,
  title,
}: {
  description?: string
  lang?: string
  meta: ConcatArray<
    | { name: string; content: string; property?: undefined }
    | { property: string; content: string; name?: undefined }
  >
  title?: string
}): JSX.Element => {

WordPressの画像とgatsby-plugin-image

blog-post.tsxは廃止されたgatsby-imageを使ってるのでgatsby-plugin-imageに変更する。
WordPressの画像の型はGatsbyTypes.WpNodeWithFeaturedImageToMediaItemConnectionEdgeだが、画像がある場所が深すぎるのでコンポーネントを作った方が手っ取り早いと思った。

GraphQLを変更して、

featuredImage {
  node {
    altText
    localFile {
      childImageSharp {
        gatsbyImageData
      }
    }
  }
}

コンポーネント用意して、

/**
 * WPから取得した画像をGatsbyImageで表示する
 */
import * as React from 'react'
import { GatsbyImage } from 'gatsby-plugin-image'

type WpImageProps = {
  image?: GatsbyTypes.WpNodeWithFeaturedImageToMediaItemConnectionEdge
  style?: React.CSSProperties
}

const WpImage = ({ image, style }: WpImageProps): JSX.Element | null => {
  const data = image?.node?.localFile?.childImageSharp?.gatsbyImageData
  const alt = image?.node?.altText || ''

  if (!image || !data) {
    return null
  }

  return <GatsbyImage image={data} alt={alt} style={style} property='image' />
}

export default WpImage

Imageと置き換える。

import WpImage from '../components/wp-image'

{/* if we have a featured image for this post let's display it */}
<WpImage image={post.featuredImage} style={{ marginBottom: 50 }} />

いちいち条件分岐する必要もなくなる。

型指定メモ

出現率高いと思うやつ

DOM要素に対するuseRef()

const inputEl = React.useRef() as React.MutableRefObject<HTMLInputElement>

DOM要素のEvent

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {}

@emotion/reactのcssプロパティ

import { css, Interpolation, Theme } from '@emotion/react'

type TagCloudProps = {
  css?: Interpolation<Theme>
}

const TagCloud: React.FC<TagCloudProps> = (
  props: TagCloudProps
): JSX.Element => {

画像とかをimportすると怒られる件

型定義ファイル作ってモジュール定義する

// src/@types/global.d.ts
declare module '*.png';
declare module '*.jpg';
declare module '*.svg';

Repo

gatsby-ts-startar

gatsby-ts-wordpress

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください