復習がてら公式のスターター(gatsby-starter-minimal、gatsby-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だけかenvとparserOptionsの両方を使用する。
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の中身を移植する。__dirname は path.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.Maybyは type 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';