React 環境へ GraphQL Code Generator を導入

GraphQL Code Generator は GraphQL のスキーマ情報やオペレーションコードをもとにし、アプリケーションが触るコード生成を行うことができるツールです。プラグイン形式で機能追加できる特徴があり、プロダクトの環境によって様々なコード生成を行うことができます。

今回は React 環境に GraphQL Code Generator を導入する手順を紹介します。

モチベーション

GraphQL のスキーマやオペレーションコードから、フロントエンド用にコード生成するツールとして最もポピュラーなのは apollo-tooling でしょうか。というのも Apollo 自体がポピュラーであり、そのドキュメントに記載があるためにこれを利用するケースが多いと思われます。apollo-tooling でも開発に問題は有りませんが、 GraphQL Code Generator を使うとより有益になる可能性があります。

先にも述べたとおり GraphQL Code Generatorプラグイン形式で機能を追加できます。React かつ Next.js を利用している場合、React 用のプラグイン、Next.js 用のプラグインが利用できます。Vue.js 用もありますし、Angular 用もあります。他にも、プロダクトで利用されうる技術ごとに様々なプラグインも用意されています。これが意味するのは、GraphQL Code Generator はアプリケーションの構成ごとに最適な機能を提供しているために、よりプロダクトの実態に合ったコード生成を実現できるということが言えます。

プロダクト構成

シンプルに考え、以下のような構成であるとします(Next.js の構成となっていますが、Next.js 特有の GraphQL Code Generator plugin は今回利用しません)。

- src/
  - pages/
    - _app.tsx
    - index.tsx // 今回関係ある
- graphql/schema.graphql // 今回関係ある
- package.json
- tsconfig.json
- node_modules
- .next

// 今回関係ある と書かれたファイルだけが今回取り扱うファイルたちです。

index.tsx はこのアプリケーション唯一の画面でありロジックです。ユーザが投稿した記事を表示します。関連する GraphQL 部分のコードだけ以下に示します。

// index.tsx
 export const ENTRY_QUERY = gql`
   query GetEntryQuery($id: String!) {
     entry
   }
 `;
 
 ...
 const { data } = useQuery<EntryQuery, EntryQueryVariables>(
     ENTRY_QUERY,
     {
       variables: {
         id: id,
       },
       skip: !id,
     }
   );

schema.graphql は GraphQL のスキーマです。これは API 側の実装によって作成されたものです。スキーマAPI から配信しても良いのですが、今回はファイルをそのまま利用することにし、ここに置いています。

今回のような構成では、例えば以下のようなコード生成が可能です。

query や mutation などのオペレーション定義から React の Hooks を作成できる(コード量を削減できる)

GraphQL Code Generator 導入前、React 内では以下のように GraphQL のコードを書いていました。

// index.tsx
 export const ENTRY_QUERY = gql` // ENTRY_QUERY に注目
   query GetEntryQuery($id: String!) {
   ....
   }
 `;
 
 ...
 const { data } = useQuery<EntryQuery, EntryQueryVariables>( // useQuery に注目
     ENTRY_QUERY, // ENTRY_QUERY に注目
     {
       variables: {
         id: id,
       },
       skip: !id,
     }
   );

export const ENTRY_QUERYuseQuerytsx の外、例えばテストコードで使うために宣言していたものです。quey を叩くには useQuery を使い、ジェネリクスに型を指定します。

これでも問題はありませんが、GraphQL Code Generator を使うとよりコード量が削減できます。useQuery を直接使わずに通信ができるようオペレーションごとに専用の Hooks が用意されるようになり、また export const ENTRY_QUERY 相当のコードが graphql-codegen によって生成されるようになります。

 ...
 // useQuery 相当
 export function useGetEntryQuery(baseOptions?: Apollo.QueryHookOptions<GetEntryQuery, GetEntryQueryVariables>) {}
 
 // useLazyQuery 相当
 export function useGetEntryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetEntryQuery, GetEntryQueryVariables>) {}
 
 // ENTRY_QUERY 相当
 export const GetEntryDocument = gql`
     query GetEntry($id: String!) {
       entry(id: $id) {
         id
 ...

よって ENTRY_QUERY というような変数に入れなくとも、以下のようにできます。

 import { GetEntryDocument, useGetEntryQuery } from "generated/graphql";
 
  gql` // ENTRY_QUERY に代入しなくなった
    query EntryQuery($id: String!) {
    ....
    }
  `;
  ...
    const { data } = useGetEntryQuery({ // かつての useQuery に変わって生成された Hooks を使う
      variables: {
        id: id,
      },
      skip: !id,
    });
    ...

Enum を封印できる

TypeScript | GraphQL Code Generator にある enumsAsTypestrue にすれば、生成されるコードから enum を排除し、代わりに Union Types とすることができます。

// enumsAsTypes 設定無し
export enum Animal {
  Dog = 'DOG',
  Cat = 'CAT
}
// enumsAsTypes true
export type Animal = 
  | 'DOG'
  | 'CAT';

TypeScript の Enum は何かと話題が多いもので、気をつけて利用するよりもいっその事利用しないプロダクトも多いと思うので、こういうオプションがあるのはありがたいです。

Install

graphql-code-generator.com

npm install --save-dev @graphql-codegen/cli

npx graphql-codegen init するとセットアップウィザードが始まるので、答えていきます。

What type of application are you building?

$ npx graphql-codegen init
 
     Welcome to GraphQL Code Generator!
     Answer few questions and we will setup everything for you.
 
 ? What type of application are you building? (Press <space> to select, <a> to toggle all, <i> to invert selection)
 ❯◯ Backend - API or server
  ◯ Application built with Angular
  ◉ Application built with React
  ◯ Application built with Stencil
  ◯ Application built with other framework or vanilla JS

導入するプロダクトの利用フレームワークを聞かれています。今回は React で行っているので、Application build with React を選択します。

Where is your schema?

? Where is your schema?: (path or url) (http://localhost:4000)

GraphQL スキーマの場所を聞かれています。スキーマが URL を叩くことで得れれるなら URL、直接ファイルを触るなら .graphql の場所を指定します。今回は graphql/schema.graphql にあるので graphql/schema.graphql と指定しました。graphql/ ディレクトリ以下に複数の graphql ファイルがある場合は graphql/*.graphql とします。

Where are your operations and fragments?

? Where are your operations and fragments?: (src/**/*.graphql)

ここで要求されているのは、クライアントサイドで書かれている query や mutation といったオペレーションの定義です。今回の場合、定義は src ディレクトリ以下にある tsx にかかれています。また今後 tsx だけではなく ts ファイルにもオペレーションを書く可能性を考えて、ここに書かれているような *.graphql ではなく "src/**/!(*.d).{ts,tsx}" と指定します。

Pick plugins

 ? Pick plugins: (Press <space> to select, <a> to toggle all, <i> to invert selection)
 ❯◉ TypeScript (required by other typescript plugins)
  ◉ TypeScript Operations (operations and fragments)
  ◉ TypeScript React Apollo (typed components and HOCs)
  ◯ TypeScript GraphQL files modules (declarations for .graphql files)
  ◯ TypeScript GraphQL document nodes (embedded GraphQL document)
  ◯ Introspection Fragment Matcher (for Apollo Client)

プラグインを選択します。ドキュメントにそれぞれ書いてあるので見てみましょう。

? Where to write the output

? Where to write the output: (src/generated/graphql.tsx)

GraphQL Code Generator が生成したコードを置く場所を指定できます。tsx である必要はないので src/generated/graphql.ts とします。

Do you want to generate an introspection file?

? Do you want to generate an introspection file? (Y/n)

introspection file が必要なら Y を指定しましょう。ここでは必要ないので n とします。

How to name the config file?

How to name the config file? (codegen.yml)

GraphQL Code Generator は yml で設定ファイルを記述します(今までウィザードで聞かれていた設定はここに書かれます)。こだわりが無ければこのままで良いと思うのでこのまま codegen.yml で進めます。

What script in package.json should run the codegen?

What script in package.json should run the codegen?

GraphQL Code Generator がコード生成するスクリプトを package.json に用意してくれますが、そのスクリプト名を設定できます。今回は codegen としました。

     Config file generated at codegen.yml
 
       $ npm install
 
     To install the plugins.
 
       $ npm run codegen
 
     To run GraphQL Code Generator.

完了するとこのようなメッセージが出てきます。npm i して npm run codegen すると src/generated/graphql.ts が吐き出されるはずです。

ちなみに package.json には以下のようなものが追加されており

...
     "codegen": "graphql-codegen --config codegen.yml"
"devDependencies": {
     ...
     "@graphql-codegen/typescript": "1.17.9",
     "@graphql-codegen/typescript-operations": "1.17.8",
     "@graphql-codegen/typescript-react-apollo": "2.0.6",
...

codegen.yml は新規に作成されています。

 overwrite: true
 schema: "graphql/schema.graphql"
 documents: "src/**/!(*.d).{ts,tsx}"
 generates:
   src/generated/graphql.tsx:
     plugins:
       - "typescript"
       - "typescript-operations"
       - "typescript-react-apollo"

GraphQL Code Generator を理解していれば、ウィザードを使わずにこれらを直接編集しても良さそうです。

ESLint の設定

ESLint を設定されているプロダクトがほとんどだと思います。このままでは src/generated/graphql.ts のコードに対し ESLint から大量の怒られが発生するので、除外する設定を書いておくと良さそうです。

gnorePatterns: ["src/generated/graphql.ts"],

Enum 禁止

codegen.yml に enumsAsTypes: true を追加することで、Enum ではなく Union Types にできます。必要であれば指定しておくと良いです。

 overwrite: true
 schema: "graphql/schema.graphql"
 documents: "src/**/!(*.d).{ts,tsx}"
 generates:
   src/generated/graphql.tsx:
     plugins:
       - "typescript"
       - "typescript-operations"
       - "typescript-react-apollo"
    config:
      enumsAsTypes: true // これ