TypeScript: graphql-codegenでスキーマの型定義と操作の型定義を分けて生成する方法

本稿では、graphql-codegenでTypeScriptの型定義を生成するとき、スキーマの型定義と操作の型定義を別々に生成する方法を紹介します。

上記を実現するのに重要になってくる`import-types`の使い方を例示しながら説明します。

## 生成元となるコードの準備

まず、生成元となる次のコードを準備します:

1. schema.gql: GraphQLのスキーマファイル
1. queries.gql: 操作を記述したファイル

それぞれ内容は次の通り:

```graphql:schema.gql
scalar DateTime

type BlogPost {
  id: ID!
  title: String!
  description: String
  body: String!
  author: Author!
  createdOn: DateTime!
}

type Author {
  id: ID!
  name: String!
  email: String!
}

type Query {
  allBlogPosts: [BlogPost!]!
  blogPost(id: ID!): BlogPost
}

type Mutation {
  createBlogPost(input: CreateBlogPostInput): BlogPost
}

input CreateBlogPostInput {
  title: String!
  description: String
  body: String!
  authorId: ID!
}
```

```graphql:queries.gql
query GetBlogPost($id: ID!) {
  blogPost(id: $id) {
    id
    title
    description
    author {
      id
      name
    }
    createdOn
  }
}
```

ここからgraphql-codegenを使い、TypeScriptの型定義ファイルを2つ生成します。

## 必要なツールの導入

graphql-codegenをはじめTypeScriptの型ファイル生成に必要なツールをインストールします:

```shell
yarn add graphql

yarn add -D typescript \
  @graphql-codegen/cli \
  @graphql-codegen/import-types-preset \
  @graphql-codegen/typescript \
  @graphql-codegen/typescript-operations \
  prettier
```

このうち、`@graphql-codegen/import-types-preset`が重要です。これを入れると、スキーマと操作の型定義を別々のファイルとして生成できるようになります。


## graphql-codegenの設定

続いてgraphql-codegenの設定をしていきます。codegen.ymlを作り、内容は次のようにします:

```codegen.yml
overwrite: true
schema: "./schema.gql"
documents:
  - queries.gql
generates:
  # スキーマの型定義
  types.ts: # [*1]
    plugins:
      - typescript
    config:
      scalars:
        DateTime: Date
    hooks:
      afterOneFileWrite:
        - prettier --write

  # 操作の型定義
  operations.ts:
    preset: import-types
    presetConfig:
      typesPath: ./types # [*1]から拡張子tsを除いたファイル名を指定します
    plugins:
      - typescript-operations
    hooks:
      afterOneFileWrite:
        - prettier --write
```

この設定で、graphql-codegenを実行すると、次の2つのファイルが生成されます:

- types.ts
- operations.ts

それぞれの中身は次のようになります:

```types.ts
export type Maybe<T> = T | null;
export type Exact<T extends { [key: string]: unknown }> = {
  [K in keyof T]: T[K];
};
export type MakeOptional<T, K extends keyof T> = Omit<T, K> &
  { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> &
  { [SubKey in K]: Maybe<T[SubKey]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
  DateTime: Date;
};

export type Author = {
  __typename?: "Author";
  id: Scalars["ID"];
  name: Scalars["String"];
  email: Scalars["String"];
};

export type BlogPost = {
  __typename?: "BlogPost";
  id: Scalars["ID"];
  title: Scalars["String"];
  description?: Maybe<Scalars["String"]>;
  body: Scalars["String"];
  author: Author;
  createdOn: Scalars["DateTime"];
};

export type CreateBlogPostInput = {
  title: Scalars["String"];
  description?: Maybe<Scalars["String"]>;
  body: Scalars["String"];
  authorId: Scalars["ID"];
};

export type Mutation = {
  __typename?: "Mutation";
  createBlogPost?: Maybe<BlogPost>;
};

export type MutationCreateBlogPostArgs = {
  input?: Maybe<CreateBlogPostInput>;
};

export type Query = {
  __typename?: "Query";
  allBlogPosts: Array<BlogPost>;
  blogPost?: Maybe<BlogPost>;
};

export type QueryBlogPostArgs = {
  id: Scalars["ID"];
};
```

```operations.ts
import * as Types from "./types";

export type GetBlogPostQueryVariables = Types.Exact<{
  id: Types.Scalars["ID"];
}>;

export type GetBlogPostQuery = { __typename?: "Query" } & {
  blogPost?: Types.Maybe<
    { __typename?: "BlogPost" } & Pick<
      Types.BlogPost,
      "id" | "title" | "description" | "createdOn"
    > & {
        author: { __typename?: "Author" } & Pick<Types.Author, "id" | "name">;
      }
  >;
};
```

これを見て分かるとおり、operations.tsはtypes.tsの型をimportして使っています。

## おわり

この投稿で例示したコードはGitHubにありますのでご参考にどうぞ:
https://github.com/suinplayground/graphql-codegen-split-types-and-operation