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