🐖

GraphQLのダイエット術 TypeScript Language Service Pluginで未使用フィールドをなくす

2024/11/16に公開

はじめに

GraphQLは、API開発において柔軟性と効率性を提供する強力なツールです。しかし、クエリの記述が自由な分、クライアント側で不用意に多くのフィールドを要求してしまうと、パフォーマンス低下や保守性の悪化につながる可能性があります🥲

本記事では、TypeScript Language Service Pluginの1つであるGraphQLSPを活用し、GraphQLクエリの"ダイエット"、つまり未使用フィールドの削減を実現する方法を紹介します。

※ この記事はTSKaigi Kansai 2024で共有した内容を記事にしたものです。

https://kansai.tskaigi.org/talks/yamatsum

GraphQLの課題と対策

over fetching問題

GraphQLは、クライアントが厳密に必要とするデータのみを要求できるため、REST APIに比べてオーバーフェッチによるパフォーマンス低下を抑えることができます✊しかし、GraphQLでも、複雑なデータ構造を持つ場合や、複数のコンポーネントで共通のデータを使用する場合には、意図せず不要なデータを取得してしまうover fetchingが発生する可能性があります。

このover fetching問題に対する一般的な対策として、fragment collocationが挙げられます。

📖 fragment collocationとは

fragment collocationとは、GraphQLにおけるfragment (断片) を、それを利用するコンポーネントの近くに配置するという考え方です。

より具体的に説明すると、あるコンポーネントで必要なデータの断片を、そのコンポーネントのファイル内に定義されたfragmentとして記述します。これにより、以下のメリットが得られます。

  • コンポーネントとデータの密結合: コンポーネントと必要なデータが同じ場所に記述されるため、コードの可読性が高まり、保守性が向上します。
  • 重複の排除: 共通のデータ部分をfragmentとして定義し、複数のコンポーネントで再利用することで、クエリの重複を減らすことができます。
  • データのローカル化: 各コンポーネントが自身の必要なデータのみを管理するため、データフローが明確になり、デバッグが容易になります。

fragment collocationの限界

fragment collocationはover fetchingを効果的に抑制する手法ですが、fragment内で定義されたフィールドが実際に使用されているかどうかを自動的に判断することはできません。開発者が意図せず不要なフィールドを残してしまう可能性があり、依然としてover fetchingが発生するリスクが残ります🤔💭

つまり、fragment collocationは強力なツールですが、over fetching問題を完全に解決するためには、開発者が積極的に未使用フィールドを検出し、削除する必要があります。

未使用フィールドの検出

そこで未使用フィールドの機械的な検出のためのアプローチとしてTypeScript Language Service Pluginを利用しようというのが、本記事のテーマになります✨

TypeScript Language Service Plugin

TypeScript Language Service Pluginは、TypeScriptの開発体験を向上させるための拡張機能です。TypeScriptの言語サーバーにプラグインとして組み込むことで、エディタやIDEの機能を拡張し、より高度なコード補完、型チェックなどを実現します。

https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin

代表的なPlugin

cssのstyled componentのためのプラグイン。
シンタックスエラーやホバーでの情報表示などが可能。

https://github.com/styled-components/typescript-styled-plugin

GraphQLのクライアント開発向けのプラグイン。
本記事で紹介するプラグインとほぼ同等の機能が提供されており、Fragmentの定義ジャンプも可能。

https://github.com/Quramy/ts-graphql-plugin

Next.jsのBuildinプラグイン。
ドキュメントには明言されていませんが、こちらもTypeScript Language Service Pluginで実現されており、RSC内でhooksを利用したりすると警告が表示される。

https://x.com/asidorenko_/status/1833250904608539066

該当のコードはこちら
https://github.com/vercel/next.js/blob/main/packages/next/src/server/typescript/index.ts

未使用フィールド検出の仕組み

  1. TypeScriptのASTの解析: TypeScriptのソースファイルをASTに変換します。これはTypeScriptのコンパイラAPIを使用して行います。ASTを解析し、GraphQLのクエリやフラグメントが含まれるノードを見つけ出します。

  2. GraphQLのクエリやフラグメントの抽出: AST内のすべてのGraphQLの呼び出し式を見つけます。この関数は、graphqlgqlといったタグ付きテンプレートリテラルを探します。見つけた呼び出し式から、GraphQLのクエリやフラグメントを抽出します。

  3. GraphQLのASTの解析とフィールドの収集: GraphQLのクエリやフラグメントを含むASTノードを解析し、すべてのフィールドを収集します。解析はGraphQLのパーサーを使ってGraphQL ASTに変換します

  4. フィールドの使用状況の確認: 収集したフィールドがコード内で使用されているかどうかを確認します。各フィールドの参照を検索し、参照が存在するかどうかを確認します。

  5. 未使用フィールドの特定: 使用されていないフィールドを特定し、エディタの警告メッセージ(diagnostics API)として報告します。

これらの仕組みがライブラリとして提供されているので、ご紹介します!

GraphQLSP

それがGraphQLSPになります(名前がややこしい😣)
GraphQLSPは警告、コード補完、ホバー情報を提供するTypeScript Language Service Pluginになります。

https://github.com/0no-co/GraphQLSP

📖 0no-coについて

開発者エクスペリエンス、Webパフォーマンス、GraphQLなどを専門とする会社で、
GraphQLクライアントのurqlやGraphQL-Codegenの代替となるgql.tadaのメンテナが集まる組織になっています。

https://0no.co/

主な機能

機能 説明
ホバー情報 フィールドの上にマウスカーソルを合わせると、そのフィールドの説明が表示されます
非推奨フィールドの警告 非推奨のフィールドを使用した場合、警告が表示されます
引数の型不一致の検出 引数の型が間違っている場合、エラーが表示されます
フィールドの自動補完 エディタ内でフィールド名をタイプすると、自動的に候補が表示されます

実例

ではGraphQLSPを使った未使用フィールドの検出の実例を示します。
Reactを使った簡単なサンプルを見てみます。
以下のコードではpokemonのidとnameを取得しています。
このとき、コンポーネント側でnameは表示させる必要がなく、代わりにidのみを表示させることにします。

import React from 'react';
import { useQuery } from 'urql';
import { graphql } from './gql';

const pokemonsQueryDocument = graphql(`
  query Pokemons {
    pokemons(limit: 10) {
      id
      name
    }
  }
`);

export const PokemonList = () => {
  const [result] = useQuery({ query: pokemonsQueryDocument });
  const { data } = result;

  return (
    <div>
      {data && (
        <ul>
          {data.pokemons.map(pokemon => (
-            <li id={pokemon.id}>{pokemon.name}</li>
+            <li id={pokemon.id}>{pokemon.id}</li>
          ))}
        </ul>
      )}
    </div>
  );
};

すると、document側ではnameが使われていないため、未使用フィールドとして警告が表示されるようになります🎉
あとはnameフィールドを消してあげればover fetchingされることはありません😋


VSCodeを利用している場合

まとめ

TypeScript Language Service PluginであるGraphQLSPを用いることで未使用フィールドの検出を行うことができました!最近ではReact Server Componentsの登場によってGraphQLと比較されることがありますが、GraphQLのエコシステムもまだまだ進化しているようです!

令和トラベル Tech Blog

Discussion