Skip to main content

Visitor Pattern

Most of the codegen's plugins are written with a design-pattern called Visitor. GraphQL has an internal mechanism for "visiting" a GraphQLSchema and GraphQL operations, and you can use it to transform your GraphQL definitions into a custom output.

With visitor pattern you can call a custom function on each AST node, and transform it into something else.

You can use ASTExplorer and see how does GraphQL represents it's definitions in a JSON structure, and you can also use this to understand which function will be called each time.

In graphql.org you can find the exact API documentation we are going to 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 { printSchema, parse } = require('graphql');
module.exports = {
plugin: (schema, documents, config) => {
const printedSchema = printSchema(schema); // Returns a string representation of the schema
const astNode = parse(printedSchema); // Transforms the string into ASTNode
},
};

Note: if you wish to have GraphQL directives when you print your schema, use printSchemaWithDirectives from graphql-toolkit package.

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 { printSchema, parse, visit } = require('graphql');
module.exports = {
plugin: (schema, documents, config) => {
const printedSchema = printSchema(schema); // Returns a string representation of the schema
const astNode = parse(printedSchema); // Transforms the string into ASTNode
const visitor = {
FieldDefinition: node => {
// This function triggered per each field
},
ObjectTypeDefinition: node => {
// This function triggered per each type
},
};
const result = visit(astNode, { leave: visitor });
return result.definitions.join('\n');
},
};

Now, let's implement ObjectTypeDefinition and FieldDefinition:

const { printSchema, parse, visit } = require('graphql');
module.exports = {
plugin: (schema, documents, config) => {
const printedSchema = printSchema(schema); // Returns a string representation of the schema
const astNode = parse(printedSchema); // Transforms the string 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 = visit(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 visitor pattern.

All those utils are part of @graphql-codegen/visitor-plugin-common package.

It includes set of Visitor classes that you can use and extend, to implement your plugin easily:

  • BaseVisitor is a class that contains a very basic implementation and utils for plugin configuration, and let you easily implement plugins that compatiable with namingConvention and scalars configuration. Here you can find an example for using it.

  • BaseTypesVisitor is a class that contains implementation for converting types, interfaces, unions, enums and fields. It's the base implementation for flow and typescript plugins.

  • BaseResolversVisitor is a class that contains implementation for generating a resolvers signature, it's the base implementation for flow-resolvers and typescript-resolvers

  • BaseDocumentsVisitor is class that contains implementation for transforming GraphQL operations (query/mutation/subscription/fragment) with a resursive handler for selection-sets. It's the base implementation for flow-operations and typescript-operations

  • ClientSideBaseVisitor is a class that contains implementation for creating client-side code for consuming GraphQL operations, it's in use by typescript-apollo-angular, typescript-react-apollo, typescript-vue-apollo and typescript-apollo-stencil plugins.

You can use the above classes as base, and extend it as you wish, to create a custom plugin.