Visitor Pattern
Most of the codegen’s plugins are written with a design-pattern called Visitor.
In addition, GraphQL has an internal mechanism for “visiting” GraphQLSchema
and GraphQL operations, and you can use it to transform your GraphQL definitions into a custom output.
You can call a custom function on each AST node and transform it into something else with a visitor pattern.
You can use ASTExplorer and see how GraphQL represents its definitions in a JSON structure. You can also use this to understand which function will be called each time.
In graphql.org you can find the detailed API documentation we will use in this section.
Basic Visitor
In this example, we will transform a basic type definition into a list of types and fields:
From:
type MyType {
myField: String!
}
type MyOtherType {
myOtherField: Int!
}
To
MyType.myField
MyOtherType.myOtherField
To get started with a basic visitor, start by extracting the astNode
of your GraphQLSchema
:
const { getCachedDocumentNodeFromSchema } = require('@graphql-codegen/plugin-helpers')
module.exports = {
plugin(schema, documents, config) {
const astNode = getCachedDocumentNodeFromSchema(schema) // Transforms the GraphQLSchema into ASTNode
}
}
Then, create your initial visitor, in our case, we would like to transform a FieldDefinition
and ObjectTypeDefinition
, so let’s create an object with a stub definitions, an use visit
to run it:
const { getCachedDocumentNodeFromSchema, oldVisit } = require('@graphql-codegen/plugin-helpers')
module.exports = {
plugin(schema, documents, config) {
const astNode = getCachedDocumentNodeFromSchema(schema) // Transforms the GraphQLSchema into ASTNode
const visitor = {
FieldDefinition(node) {
// This function triggered per each field
},
ObjectTypeDefinition(node) {
// This function triggered per each type
}
}
const result = oldVisit(astNode, { leave: visitor })
return result.definitions.join('\n')
}
}
Now, let’s implement ObjectTypeDefinition
and FieldDefinition
:
const { getCachedDocumentNodeFromSchema, oldVisit } = require('@graphql-codegen/plugin-helpers')
module.exports = {
plugin(schema, documents, config) {
const astNode = getCachedDocumentNodeFromSchema(schema) // Transforms the GraphQLSchema into ASTNode
const visitor = {
FieldDefinition(node) {
// Transform the field AST node into a string, containing only the name of the field
return node.name.value
},
ObjectTypeDefinition(node) {
// "node.fields" is an array of strings, because we transformed it using "FieldDefinition".
return node.fields.map(field => `${node.name.value}.${field}`).join('\n')
}
}
const result = oldVisit(astNode, { leave: visitor })
return result.definitions.join('\n')
}
}
Codegen and Visitors
This repository also contains a set of utils that might help you to write plugins faster using the visitor pattern.
All those utils are part of @graphql-codegen/visitor-plugin-common
package.
It includes a set of Visitor classes that you can use and extend to implement your plugin quickly:
For example, BaseVisitor
is a class that contains a simple implementation and utils for plugin configuration and lets you quickly implement plugins compatible with namingConvention
and scalars
configuration.
You can find an example for using it here.
-
BaseTypesVisitor
is a class that contains implementation for converting types, interfaces, unions, enums, and fields. It’s the base implementation forflow
andtypescript
plugins -
BaseResolversVisitor
is a class that contains implementation for generating a resolvers signature, it’s the base implementation forflow-resolvers
andtypescript-resolvers
-
BaseDocumentsVisitor
is class that contains implementation for transforming GraphQL operations (query/mutation/subscription/fragment) with a recursive handler for selection-sets. It’s the base implementation forflow-operations
andtypescript-operations
-
ClientSideBaseVisitor
is a class that contains implementation for creating client-side code for consuming GraphQL operations, it’s in use bytypescript-apollo-angular
,typescript-react-apollo
,typescript-vue-apollo
andtypescript-apollo-stencil
plugins
To create a custom plugin, you can use the above classes as a base and extend it as you wish.