GraphQL Code Generator plugin for generating TypeScript modules with embedded GraphQL document nodes
npx @tessl/cli install tessl/npm-graphql-codegen--typescript-document-nodes@4.0.0A GraphQL Code Generator plugin that generates TypeScript modules with embedded GraphQL document nodes, enabling strongly-typed GraphQL operations with compile-time safety and automatic fragment resolution.
npm install @graphql-codegen/typescript-document-nodes// ES6 modules
import {
plugin,
validate,
TypeScriptDocumentNodesVisitor
} from "@graphql-codegen/typescript-document-nodes";
import type {
TypeScriptDocumentNodesRawPluginConfig,
TypeScriptDocumentNodesPluginConfig
} from "@graphql-codegen/typescript-document-nodes";
import type { Types } from "@graphql-codegen/plugin-helpers";
import type { GraphQLSchema } from "graphql";
// CommonJS
const {
plugin,
validate,
TypeScriptDocumentNodesVisitor
} = require("@graphql-codegen/typescript-document-nodes");
const { Types } = require("@graphql-codegen/plugin-helpers");This plugin is designed to be used with GraphQL Code Generator to transform GraphQL documents into TypeScript modules with embedded document nodes:
// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: 'schema.graphql',
documents: 'src/**/*.graphql',
generates: {
'src/generated/document-nodes.ts': {
plugins: ['typescript-document-nodes']
}
}
};
export default config;Given a GraphQL query file:
# queries/user.graphql
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}The plugin generates:
export const GetUser = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;/**
* Main plugin function that processes GraphQL documents and generates TypeScript modules
* @param schema - GraphQL schema object (can be null)
* @param documents - Array of GraphQL document files
* @param config - Plugin configuration options
* @returns Generated TypeScript code with imports and content
*/
function plugin(
schema: GraphQLSchema | null,
documents: Types.DocumentFile[],
config: TypeScriptDocumentNodesRawPluginConfig
): Types.ComplexPluginOutput;/**
* Configuration options for the TypeScript Document Nodes plugin (user input)
*/
interface TypeScriptDocumentNodesRawPluginConfig extends RawClientSideBasePluginConfig {
/**
* Naming convention for generated variable names
* @default "change-case-all#pascalCase"
*/
namingConvention?: NamingConvention;
/**
* Prefix added to generated variable names
* @default ""
*/
namePrefix?: string;
/**
* Suffix added to generated variable names
* @default ""
*/
nameSuffix?: string;
/**
* Prefix added to fragment variable names
* @default ""
*/
fragmentPrefix?: string;
/**
* Suffix added to fragment variable names
* @default ""
*/
fragmentSuffix?: string;
/**
* External fragments to include in the visitor
* @default []
*/
externalFragments?: LoadedFragment[];
}
/**
* Processed configuration interface used internally by the visitor
*/
interface TypeScriptDocumentNodesPluginConfig extends ClientSideBasePluginConfig {
/**
* Naming convention for generated variable names
*/
namingConvention: NamingConvention;
/**
* Whether to transform underscores in names
*/
transformUnderscore: boolean;
}/**
* Fragment definition loaded into the visitor
*/
interface LoadedFragment {
/** Fragment name */
name: string;
/** Type this fragment is defined on */
onType: string;
/** GraphQL fragment definition node */
node: FragmentDefinitionNode;
/** Whether this fragment is external to current document set */
isExternal: boolean;
/** Import source if external fragment */
importFrom?: string | null;
}
/**
* Document file containing GraphQL operations
*/
interface DocumentFile {
/** File location path */
location: string;
/** Parsed GraphQL document node */
document: DocumentNode;
}
/**
* Plugin output with prepend imports and content
*/
interface ComplexPluginOutput {
/** Import statements to prepend */
prepend: string[];
/** Generated content */
content: string;
}/**
* Validates plugin configuration and output file requirements
* @param schema - GraphQL schema object (can be null)
* @param documents - Array of GraphQL document files
* @param config - Plugin configuration
* @param outputFile - Output file path
* @throws Error if output file doesn't end with .ts
*/
function validate(
schema: GraphQLSchema | null,
documents: Types.DocumentFile[],
config: any,
outputFile: string
): Promise<void>;/**
* Visitor class that handles document traversal and code generation
* This class is exported and available for extension or custom implementations
*/
class TypeScriptDocumentNodesVisitor extends ClientSideBaseVisitor<
TypeScriptDocumentNodesRawPluginConfig,
TypeScriptDocumentNodesPluginConfig
> {
/**
* Creates a new visitor instance
* @param schema - GraphQL schema
* @param fragments - Array of loaded fragments
* @param rawConfig - Raw plugin configuration
* @param documents - Array of document files
*/
constructor(
schema: GraphQLSchema,
fragments: LoadedFragment[],
rawConfig: TypeScriptDocumentNodesRawPluginConfig,
documents: Types.DocumentFile[]
);
/**
* Returns import statements to be prepended to generated output
* Inherited from ClientSideBaseVisitor
*/
getImports(): string[];
/**
* Fragment definitions as string
* Inherited from ClientSideBaseVisitor
*/
fragments: string;
}// Configuration
const config: TypeScriptDocumentNodesRawPluginConfig = {};
// Usage with GraphQL Code Generator
const result = plugin(schema, documents, config);
// Generates: export const MyQuery = gql`query MyQuery { ... }`;// Using camelCase naming
const config: TypeScriptDocumentNodesRawPluginConfig = {
namingConvention: 'change-case-all#camelCase'
};
// Generated output changes from MyQuery to myQuery// Adding prefixes and suffixes
const config: TypeScriptDocumentNodesRawPluginConfig = {
namePrefix: 'Gql',
nameSuffix: 'Document',
fragmentPrefix: 'Fragment',
fragmentSuffix: 'Part'
};
// Transforms MyQuery to GqlMyQueryDocument
// Transforms UserFragment to FragmentUserFragmentPart// Input GraphQL with fragments
const fragmentQuery = `
fragment UserInfo on User {
id
name
email
}
query GetUserWithProfile($id: ID!) {
user(id: $id) {
...UserInfo
profile {
bio
}
}
}
`;
// Generated output includes fragment composition
// export const UserInfo = gql`fragment UserInfo on User { ... }`;
// export const GetUserWithProfile = gql`query GetUserWithProfile($id: ID!) { ... }\${UserInfo}`;// Complex naming and transformation options
const config: TypeScriptDocumentNodesRawPluginConfig = {
namingConvention: {
typeNames: 'change-case-all#pascalCase',
transformUnderscore: true
},
namePrefix: 'Generated',
nameSuffix: 'Query'
};
// Transforms query names with underscores and applies prefix/suffix// Including external fragments from other files
const config: TypeScriptDocumentNodesRawPluginConfig = {
externalFragments: [
{
name: 'UserFields',
onType: 'User',
node: userFieldsFragmentNode,
isExternal: true,
importFrom: './user-fragments'
}
]
};
// External fragments will be available for composition in generated queries// Processing multiple GraphQL files
const documents = [
{ location: 'queries/user.graphql', document: userQueryAst },
{ location: 'queries/posts.graphql', document: postsQueryAst },
{ location: 'fragments/common.graphql', document: fragmentsAst }
];
const result = plugin(schema, documents, config);
// Generates TypeScript constants for all named operations// Generated code can be used directly with GraphQL clients
import { GetUser } from './generated/document-nodes';
import { useQuery } from '@apollo/client';
// Apollo Client usage
const { data, loading, error } = useQuery(GetUser, {
variables: { id: '123' }
});
// Other GraphQL clients
const result = await graphqlClient.request(GetUser, { id: '123' });For advanced customization, you can extend the visitor class to modify code generation behavior:
import {
TypeScriptDocumentNodesVisitor,
TypeScriptDocumentNodesRawPluginConfig
} from "@graphql-codegen/typescript-document-nodes";
import { GraphQLSchema } from "graphql";
class CustomDocumentNodesVisitor extends TypeScriptDocumentNodesVisitor {
constructor(
schema: GraphQLSchema,
fragments: LoadedFragment[],
rawConfig: TypeScriptDocumentNodesRawPluginConfig,
documents: Types.DocumentFile[]
) {
super(schema, fragments, rawConfig, documents);
}
// Override methods to customize generation behavior
// Access to all parent class methods and properties
}
// Use custom visitor in your own plugin implementation
const customResult = oldVisit(ast, { leave: new CustomDocumentNodesVisitor(...) });The plugin enforces these validation rules:
.ts extension// Validation errors
try {
await validate(schema, documents, config, 'output.js');
} catch (error) {
// Error: Plugin "typescript-document-nodes" requires extension to be ".ts"!
}
// Configuration errors are handled during plugin execution
// Fragment resolution errors are handled during document processing// Combining with other plugins for complete type safety
const config: CodegenConfig = {
generates: {
'src/generated/types.ts': {
plugins: ['typescript']
},
'src/generated/document-nodes.ts': {
plugins: ['typescript-document-nodes'],
config: {
namingConvention: 'change-case-all#pascalCase'
}
}
}
};// Integration with build tools
import { plugin } from '@graphql-codegen/typescript-document-nodes';
// Custom build script
const generateDocuments = async () => {
const result = plugin(schema, documents, config);
await writeFile('output.ts', result.prepend.join('\n') + '\n' + result.content);
};