フロントエンドで以下のようにコード中に GraphQL を書いたとする。
code-with-queries.js
const queryA = graphql(`
query QueryA {
viewer {
...FragmentA
}
}
`)
const fragmentA = graphql(`
query FragmentA on Viewer {
id
name
...FragmentB
}
`)
const fragmentB = graphql(`
query FragmentB on Viewer {
followers(first: 3) {
node {
id
name
}
}
}
`)
ここで書いた Query を諸事情で、以下のように抜き出して利用したいことがある。
QueryA-and-dependencies.gql
# QueryA と利用している Fragment のみを抜き出す。
query QueryA {
viewer {
...FragmentA
}
}
query FragmentA on Viewer {
id
name
...FragmentB
}
query FragmentB on Viewer {
followers(first: 3) {
node {
id
name
}
}
}
こういうことは、 GraphQL-Tools の loader を使えば行える。
今回やりたいことを実際に行うサンプルコードから用意したので、これを元に解説する。
1. @graphql-tools/load
, @graphql-tools/xxx-loader
を使って GraphQL Operation を抜き出せる
graphql-tools の loader を使って、以下のように GraphQL コードを抜き出せる。
import { loadDocuments } from '@graphql-tools/load'
// 読み込む元のファイルの種類ごとの loader が用意されている
import { CodeFileLoader } from '@graphql-tools/code-file-loader'
async function main() {
const sources = await loadDocuments('./src/**/*.{ts,tsx}', {
loaders: [new CodeFileLoader()],
pluckConfig: {
globalGqlIdentifierName: 'graphql',
},
})
// ...
}
2. loader した GraphQL コードの AST を走査する
読み込んだコードは graphql-tools の Source として返されて、この中にコードの文字列やファイル名と GraphQL コードの AST が含まれている。
この AST は graphql.js の Visitor API を使って走査出来る。
import { visit } from 'graphql'
/** @typedef {import("@graphql-tools/utils").Source} Source */
/** @typedef {import("graphql").ExecutableDefinitionNode} ExecutableDefinitionNode */
class DocumentDependencyGraph {
/**
* @param {Source[]} sources
* @return {SchemaDependencyGraph}
*/
static fromSources(sources) {
const graph = new DocumentDependencyGraph()
sources.forEach(source => {
const document = source.document
let currentName = null
visit(document, {
// 各 query, mutation 宣言それぞれに対して呼び出す
OperationDefinition(node) {
if (node.name) {
currentName = node.name.value
graph.addDefinition(node.name.value, node)
}
},
// 各 fragment 宣言それぞれに対して呼び出す
FragmentDefinition(node) {
currentName = node.name.value
graph.addDefinition(node.name.value, node)
},
// `...Fragment` のような fragment を展開する箇所それぞれで呼び出す
FragmentSpread(node) {
if (currentName) {
graph.addDependency(currentName, node.name.value)
}
},
})
})
return graph
}
constructor() {
/** @type {Object<string, ExecutableDefinitionNode>} */
this.definitions = {}
/** @type {Object<string, Set<string>>} */
this.dependencies = {}
}
// ...
}
AST の各 Node の定義は ↓ にまとまっているのでこれらを参照。
自分が見た感じ、 各 Node の定義は GraphQL の Spec 内の名前とほぼ同じなので、こちらを参考にしてもよいかも。
3. print
走査した GraphQL AST は GraphQL.js の print
関数 で文字列として出力出来る。
これで、関連する fragment を含めた GraphQL operation の定義を出力できる。
import { print } from 'graphql'
/**
* @param {string} name
* @return {string}
*/
getSourceWithDependencies(name) {
const source = print(this.definitions[name])
const dependencies = this.getDependencies(name)
return (
dependencies.reduce((source, dependency) => {
const dependencySource = this.definitions[dependency]
return source + '\n\n' + print(dependencySource)
}, source) + '\n'
)
}