GraphQL Code Generator(graphql-codegen) おすすめ設定 for TypeScript
全体
生成物をフォーマッタにかける
graphql-codegen には Lifecycle Hooks という仕組みがあり、いくつかの用意された hook ポイントで任意のコマンドを実行できる。
これを使って生成されたコードを Prettier 等のフォーマッタに通しておくのがおすすめ。
hooks:
afterAllFileWrite:
- prettier --write
基本 DO NOT EDIT とはいえ、コードジャンプしてきて生成された型を読みたいケースはよくある。
そういうときのために、人間が見やすいように改行しておいてもらうといい。
typescript
plugin, typescript-operation
plugin
最も基本のプラグイン。ちなみに typescript
はスキーマ、typescript-operation
は Operation(Query, Mutation, Fragment)に関心がある。
設定できる項目は共通のものが多いのでまとめて記載。
immutableTypes
: 意図しないデータ更新を防ぐ
生成される型のフィールドに readonly
がつき、配列は ReadonlyArray
になる。
「クエリ結果を弄ってしまって意図しない箇所に影響が出る」みたいなのを型のレベルで防ぐことができる。
また、関数の引数では readonly であること == 引数を書き換えないことの宣言 になるので、そういう観点でもなるべく readonly にしておきたい。
たまにライブラリが提供する関数の型定義で readonly
ついてなくて面倒になるケースがあるけど、それはその型定義に改善余地ありなので PR を投げてあげましょう。
defaultScalarType
: custom scalar を any
にしない
Custom Scalar はデフォルトでは any
になるが、これは最悪なので別の型にマッピングしよう!という設定。
よくあるケースでは Date
や DateTime
のような custom scalar を定義したんだけど、any
になっていることで JS の Date
と勘違いして getFullYear()
呼んじゃってバグる みたいなの。
一番お手軽なのはこの defaultScalarType
で、これを unknown
にしておくことで、前述したようなバグは型チェックで落とせる。
scalars
, strictScalars
: custom scalar を型で区別できるようにする
defaultScalarType
の代わりに入れる、より高度な設定。
scalars
は各 custom scalar ごとにマッピング先の TypeScript の型を指定できる。
たとえば DateString
のような Branded Type をマッピング先に指定することで、unknown
よりもドキュメント・インタフェースとして情報量を増やすことができる。
(余談だけど、上記の記事では適当なフィールドを定義して Branded Type を作っているが、unique symbol を使うほうがより安全という話があるらしい。)
一方で、scalars
で指定が漏れた custom scalar はデフォルトにフォールバックされてしまう。それを防ぐのが strictScalars
。Branded Type を使うなら全部の custom scalar に指定しない理由はないので、scalars
と strictScalars
はセットでの運用が基本になる。
enumsAsConst
: TypeScript の enum を使わない
デフォルトだと GraphQL Enum に対して TypeScript の Enum が出力されるが、それを as const
を使った union 型に変更するオプション。
TypeScript の Enum はいろいろ悩ましいところが多いので、できるだけ利用を避けていきたい。
skipTypename
: __typename
を使うときは明示しよう
Operation 中で指定しない限りは__typename
が型に出てこなくなる。
__typename
が暗黙的に取得されてるかどうかは Client 実装によって異なるので、「自身のコード中で __typename
を使うときは明示的に指定すべき」というルール強制したい、というのがモチベーション。
avoidOptionals
: undefined
を厳密に扱う
nullable なフィールドが optional になるのを防ぐ。
デフォルトの挙動だとfield?: Maybe<T>
だが、avoidOptional
で field: Maybe<T>
になる。
指定したフィールドは null だったら素直に null 返してくる(省略されない)のであれば、実際の挙動に沿った型にしておきたい、という意図。
なお、GraphQL の引数は「値を入れない」と「null を渡す」を区別できるので、Input では avoidOptionals
は無効化しておくほうがいい場合がある。
avoidOptionals:
field: true
inputValue: false
object: true
defaultValue: false
(これ書いてて気になった: @skip
と @include
が指定されてるフィールドはどういう挙動になるんだっけ?)
add
plugin
任意の文字列を挿入できるプラグイン。
eslint-disable
や DO NOT EDIT
などのコメントを追加
graphql-codegen の出力にはヘッダコメントがつかないので、自分でつける必要がある。
自動生成であることをアピールするための DO NOT EDIT
や、ESLint の対象外であることを宣言する /* eslint-disable */
の追加がよくあるパターン。
plugins:
- add:
content: "// Code generated by graphql-codegen. DO NOTT EDIT."
scalars
オプションで使う Branded Type の定義を差し込む
scalars
のところで言及した Branded Type の指定だが、そもそもその型を生成ファイル内で使えないと話にならない。
なので、 add
plugin を使って Branded Type の定義を挿入してあげる。
plugins:
- add:
content: '// Code generated by graphql-codegen. DO NOT EDIT.'
- add:
content: 'export type DateString = string & { readonly brand: unique symbol };'
- typescript
config:
scalars:
Date: DateString
別のファイルで Branded Type を定義して import してくるのでもいいかもしれない。
typed-document-node
plugin
GraphQL Document の TypedDocumentNode
を生成する。
詳しくは以下の記事や動画を見てもらえばいいが、簡単に言うと「TypedDocumentNode
を useQuery
に渡すだけで variables やレスポンスに型がつく」もの。
JS の GraphQL エコシステムはだいたい TypedDocumentNode に対応してるので、特定のライブラリのラッパー関数を生成する plugin を使うよりも多くの場合で typed-document-node
plugin のほうがいいはず。
typedscript-msw
plugin
名前の通り、MSW のハンドラを定義するためのヘルパを生成してくれる。
だいたいこんなかんじ(例は plugin ページから引用)。
// mockGetUserQuery が生成される関数
mockGetUserQuery((req, res, ctx) => {
const { id } = req.variables // ← 型がついてる
return res(
ctx.data({
getUser: { name: 'John Doe', id } ← 型がついてる
})
)
})
テストや Storybook のお供にどうぞ。
near-operation-files
preset
GraphQL Operation に対応する生成物を、その Operation が記述されてるファイルの近くに書き出してくれる。
- Query や Fragment を使う場所の近くに型定義がある方が見やすい
- ひとつひとつの生成ファイルが小さくなって見やすい
というような生産性観点のメリットもあるし、以下の記事にあるようなパフォーマンス上のメリットもある。事情がない限りは入れておきたい。
gql-tag-operations
preset
これは超特殊だし発展途上だけど非常に良いものなので紹介… と言いたいところだが、話がかなり逸れるので別記事で紹介します。
次期メジャーである GraphQL Code Generator v3 はこの gql-tag-operations の方向に進化しそうな雰囲気もあるので楽しみですね。
さいごに
みなさんのオススメ設定もあれば是非コメントして下さい!
Discussion