Gatsby で config ファイルを TypeScript化する

Gatsby で configファイルの完全 TypeScript化について解説します。Config ファイルのそれぞれの実行環境やモジュールシステムの違いについて触れ、gatsby-ssrの特殊性についても解説しています。

2022/4/13 min read
..
hero image

はじめに

Gatsby を TypeScript 化するという名目のもと、コンポーネントや graphql を対象に解説している記事は多くあります。 しかし、Gatsby config への言及はほとんど見かけませんでした。

設定ファイルの型安全性は、コンポーネント等と同様に大切です。 この記事では、Gatsby config に焦点を当てて、 TypeScript 化の方法について解説します。

Gatsby config のモジュールシステム

まず、Gatsby config のモジュールシステムについて簡単にまとめました。

FileModule systemTypes of export
gatsby-browserES Modules | CommonJS名前付きエクスポート
gatsby-configCommonJSデフォルトエクスポート
gatsby-nodeCommonJS名前付きエクスポート
gatsby-ssrES Modules | CommonJSAPI がエクスポートされていると読み込まれる。 名前付きエクスポート

ES ModulesCommonJS のみ言及しています。

特徴としては、gatsby-configgatsby-nodeCommonJS を前提としています。 一方、 gatsby-browsergatsby-ssr は、ES ModulesCommonJS の両方を利用できます。 また、 エクスポートの方法も、 API が複数ある場合は Named Exports にする必要があります1

さらに、gatsby-ssr は少し特殊な挙動をします。 gatsby-ssr が実行されるには、Gatsby SSR APIsが名前付きエクスポートされている必要があります。

名前付きエクスポートがない場合は実行されません。

1
console.log('not exec')
gatsby-ssr.jsjs

実行される例:

1
2
3
4
5
6
7
8
9
console.log('exec')
const onRenderBody = () => {
console.log('onRenderBody')
}
export {
onRenderBody
}
gatsby-ssr.jsjs
1
2
exec
onRenderBody
bash

またデフォルトエクスポートではエクスポートしたことにはなりません。

1
2
3
4
5
const onRenderBody = () => {
console.log('not exec')
}
export default { onRenderBody }
gatsby-ssr.jsjs

さらに、すべての config に共通して、 .ts.tsx などの拡張子は、デフォルトでは読み込みません。

これらの状況を理解した上で、 Gatsby  の設定ファイルを完全に TypeScript 化し、型安全に運用できようにします。

Gatsby config の読込み順

Gatsby config は次の順に読み込まれます。

PlantUML

gatsby-config が設定ファイルの中で一番最初に読み込まれます。

そこでまずは、はじめに読み込まれる gatsby-config について見てみます。

gatsby-config を TypeScript 化する

まず、TypeScript をトランスパイルするために esbuild-registerをインストールします。

1
yarn add -D esbuild-register
bash

gatsby-config は、 CommonJS 形式の .js ファイルでなければならないため、このまま残します。 そして、新しく gatsby-config.ts をどこでもいいので作成します。 今回は ルートディレクトリ下へ作成しました。

1
2
3
4
5
6
.
├── gatsby-browser.js
├── gatsby-config.js
├── gatsby-config.ts
├── gatsby-node.js
└── gatsby-ssr.js
bash

今後、gatsby-config.ts に型付きで設定をしていくことになります。 次のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import type { GatsbyConfig } from 'gatsby'
import { resolve } from 'path'
const plugins: GatsbyConfig['plugins'] = [
'gatsby-plugin-image',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'posts',
path: resolve(__dirname, 'posts')
}
},
...
]
const siteMetadata: GatsbyConfig['siteMetadata'] = {
siteUrl: 'https://miyauchi.dev/',
}
const config: GatsbyConfig = {
siteMetadata,
plugins
}
export default config
gatsby-config.tsts

GatsbyConfig という型が提供されているので、型注釈に利用します。 最終的にデフォルトエクスポートすればいいので、適宜変数を分割できます。 あまりにも plugin が肥大化してきた場合は、ファイル分割してもいいでしょう。

パスを指定する場合は、 __dirname を使って、絶対パスで指定すると間違いが起こりにくいです。

このファイルを gatsby-config.js から読み込めばいいわけです。

esbuild-register で 実行時にトランスパイルする

esbuild-register で実行時に TypeScript をトランスパイルできます。

gatsby-config.js を次のように変更します。

1
2
3
4
5
6
7
const { register } = require('esbuild-register/dist/node')
register({
target: 'node16'
})
module.exports = require('./gatsby-config.ts')
gatsby-config.jsjs

ここでは2つのことをしています。

  • esbuild-register により、 TypeScript を esbuild でトランスパイルする
  • gatsby-config.jsgatsby-config.ts を re-export する

esbuild-registerは、ts-node と比較すると、型チェックがなく、Rust で記述されている分、高速に動作します。

これで、 gatsby-config の TypeScript 化が完了しました。

また、一番最初に読み込まれる gatsby-configesbuild-register を実行しているので、 他の config ファイルの TypeScript は自動的に esbuild でトランスパイルされます。

よって、gatsby-config 以外のファイルは、 .ts にして型注釈をつけるだけです。

gatsby-node:

1
2
3
4
5
6
7
8
9
10
11
import type { GatsbyNode } from 'gatsby'
import { resolve } from 'path'
const createPages: GatsbyNode['createPages'] = async ({
graphql,
actions,
reporter
}) => {
// ...
}
export { createPages }
gatsby-node.tsts

tsxももちろん扱うことができます。

gatsby-ssr:

1
2
3
4
5
6
7
8
9
10
import React from 'react'
import type { GatsbySSR } from 'gatsby'
const wrapPageElement: GatsbySSR['wrapPageElement'] = ({
element,
}) => {
return <div className='wrap'>{element}</div>
}
export { wrapPageElement }
gatsby-ssr.tsxtsx

gatsby-browser:

1
2
3
4
5
6
7
8
9
10
import React from 'react'
import type { GatsbyBrowser } from 'gatsby'
const wrapPageElement: GatsbyBrowser['wrapPageElement'] = ({
element,
}) => {
return <div className='wrap'>{element}</div>
}
export { wrapPageElement }
gatsby-browser.tsxtsx

基本的に 名前付きエクスポートをしなければならない点に注意してください。 Gatsby で基本的な型は提供されているので、型注釈するだけですね。

まとめ

最終的には次のファイル構造になります。

1
2
3
4
5
6
.
├── gatsby-browser.tsx
├── gatsby-config.js
├── gatsby-config.ts
├── gatsby-node.ts
└── gatsby-ssr.txs
bash

gatsby-config が最も最初に読み込まれるため、gatsby-config.js をエントリーポイントにしています。 gatsby-config.js で esbuild-register を登録することで、以降のファイルでは TypeScript がトランスパイルされます。

ただ、残念ながら gatsby-config.js をなくす方法はわかりません。 これをなくすには gatsby コマンド時に esbuild-register を登録しなければなりません。

例えば node.js ではコマンド引数として -r があり、 esbuild-register を引数に渡すことができます。

1
node -r esbuild-register a.ts
bash

これにより、TypeScript ファイルをトランスパイルし、実行することができます。 同じように、 gatsby コマンド実行時に、 esbuild-register を登録できれば、 gatsby-config.js すらなくすことができるかもしれません。

この点について知っている方いれば、コメントお願いします。

ともあれ、これで型安全な運用ができますね。


  1. ここでは TypeScript 化した際の、ES6 でのエクスポートの方法について言及しています。

Edit this page on GitHub

Other Article

Comments