gql-tag-operations-preset を使った GraphQL の Fragment Colocation
普段 GraphQL を使ったフロントエンド開発をしています。最近 GraphQL Code Generator の設定を見直しており、いくつかの plugin や preset を採用したり削除したのですが、その中でも gql-tag-operations-preset がとても良かったので、もともとの構成と gql-tag-operations-preset を導入した後の構成を比較して記事に残しておきます。
また、 gql-tag-operations-preset を導入することで、 Relay 以外の GraphQL Client でも Fragment Colocation が扱いやすくなります。
私は Apollo ユーザーなので Relay の Fragment Colocation を原則とした開発体験を羨ましく思っていたのですが、 Apollo でも少し近づいた開発体験を手に入れることができました。
gql-tag-operations-preset を導入する前の開発
gql-tag-operations-preset を導入する前は、主に typed-document-node と near-operation-file-preset を使った、 GraphQL ファイルにクエリを記述し、同じ階層に TypedDocumentNode が生成されたファイルを置く構成をとっていました。
これを実際に使用するコンポーネントと併せて同じディレクトリに入れておくことで、使わなくなったらディレクトリごと削除する運用にしていました。
└── Books
    ├── Books.tsx
    ├── books.generated.ts
    └── books.graphql
そこまで不満はなかったのですが、やはりクエリとコンポーネントはできるだけ近くにまとめておきたいとは思っていました。同じファイルにも記述できるのですが、その場合はただのテンプレート文字列でしかなく、型の恩恵を得られないのでクエリはコンポーネントとは別ファイルで記述するようにしていました。
gql-tag-operations-preset を導入した後の開発
インストール方法など、基本的にドキュメント通りなのでドキュメントを参考にしてください。
このようにコンポーネントと同じファイルに書き、型もしっかりと効いていることがわかります。

設定ファイル
gql-tag-operations-preset を導入する前と比べて、使用するパッケージも少なくなったので、GraphQL Code Generator の設定ファイルの記述量も少なくなりました。
gql-tag-operations-preset 導入前
schema: introspection.json
documents: './**/*.graphql'
generates:
  src/types/graphql.tsx:
    plugins:
      - typescript
  ./:
    preset: near-operation-file
    presetConfig:
      extension: .ts
      baseTypesPath: src/types/graphql
    plugins:
      - typescript-operations
      - typed-document-node
gql-tag-operations-preset 導入後
schema: introspection.json
documents: 'src/**/*.tsx'
generates:
  src/gql/:
    preset: gql-tag-operations-preset
便宜上簡略化しています
Fragment Colocation
Fragment Colocation についてはこちらの記事がとてもわかりやすいです。
import { useFragment, FragmentType, gql } from "../../gql";
const BooksQuery = gql(/* GraphQL */ `
  query BooksQuery {
    books {
      id
      ...BookFragment
    }
  }
`);
export const Books: VFC = () => {
  const { data } = useQuery(BooksQuery);
  return (
    <ul>
      {data.books.map((book) => (
        <li key={book.id}>
          <Book book={book} />
        </li>
      ))}
    </ul>
  );
};
const BookFragment = gql(/* GraphQL */ `
  fragment BookFragment on Book {
    title
    author
  }
`);
type Props = {
  book: FragmentType<typeof BookFragment>;
};
export const Book: VFC<Props> = (props) => {
  const book = useFragment(BookFragment, props.book);
  return (
    <>
      <p>{book.title}</p>
      <p>{book.author}</p>
    </>
  );
};
Relay ライクな useFragment もあり、 Fragment Colocation が扱いやすくなりました。
また fragmentMasking オプションを true することで、各コンポーネントはそのフラグメントによって記述されたデータ依存関係にのみアクセスすることができます。


Books コンポーネントでは BookFragment の値にはアクセスできなく、 Book コンポーネントでは BookFragment で定義された値にしかアクセスできなくなっていることがわかります。
はまったポイント
gql 関数を Apollo などの他ライブラリのものから使える augmentedModuleName オプションがあるのですが、これと fragmentMasking オプションを併用すると、 fragment の定義を読み込めずにサーバーがクエリのリクエストを処理できないエラーが発生しました。
参考
Web3スタートアップ「Gaudiy(ガウディ)」所属エンジニアの個人発信をまとめたPublicationです。公式Tech Blog:techblog.gaudiy.com/
Discussion
以下だとなぜ駄目なのでしょうか?