GraphQLのダイエット術 TypeScript Language Service Pluginで未使用フィールドをなくす
はじめに
GraphQLは、API開発において柔軟性と効率性を提供する強力なツールです。しかし、クエリの記述が自由な分、クライアント側で不用意に多くのフィールドを要求してしまうと、パフォーマンス低下や保守性の悪化につながる可能性があります🥲
本記事では、TypeScript Language Service Pluginの1つであるGraphQLSPを活用し、GraphQLクエリの"ダイエット"、つまり未使用フィールドの削減を実現する方法を紹介します。
※ この記事はTSKaigi Kansai 2024で共有した内容を記事にしたものです。
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の機能を拡張し、より高度なコード補完、型チェックなどを実現します。
代表的なPlugin
cssのstyled componentのためのプラグイン。
シンタックスエラーやホバーでの情報表示などが可能。
GraphQLのクライアント開発向けのプラグイン。
本記事で紹介するプラグインとほぼ同等の機能が提供されており、Fragmentの定義ジャンプも可能。
Next.jsのBuildinプラグイン。
ドキュメントには明言されていませんが、こちらもTypeScript Language Service Pluginで実現されており、RSC内でhooksを利用したりすると警告が表示される。
該当のコードはこちら
未使用フィールド検出の仕組み
-
TypeScriptのASTの解析: TypeScriptのソースファイルをASTに変換します。これはTypeScriptのコンパイラAPIを使用して行います。ASTを解析し、GraphQLのクエリやフラグメントが含まれるノードを見つけ出します。
-
GraphQLのクエリやフラグメントの抽出: AST内のすべてのGraphQLの呼び出し式を見つけます。この関数は、
graphql
やgql
といったタグ付きテンプレートリテラルを探します。見つけた呼び出し式から、GraphQLのクエリやフラグメントを抽出します。 -
GraphQLのASTの解析とフィールドの収集: GraphQLのクエリやフラグメントを含むASTノードを解析し、すべてのフィールドを収集します。解析はGraphQLのパーサーを使ってGraphQL ASTに変換します
-
フィールドの使用状況の確認: 収集したフィールドがコード内で使用されているかどうかを確認します。各フィールドの参照を検索し、参照が存在するかどうかを確認します。
-
未使用フィールドの特定: 使用されていないフィールドを特定し、エディタの警告メッセージ(diagnostics API)として報告します。
これらの仕組みがライブラリとして提供されているので、ご紹介します!
GraphQLSP
それがGraphQLSPになります(名前がややこしい😣)
GraphQLSPは警告、コード補完、ホバー情報を提供するTypeScript Language Service Pluginになります。
📖 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です。 「あたらしい旅行を、デザインする。」をミッションに、海外旅行におけるあたらしい体験や、あたらしい社会価値の提供を目指すデジタルトラベルエージェンシーです。海外ツアー・ホテル予約アプリ「NEWT(ニュート)」を提供しています。(NEWT:newt.net/)
Discussion