Skip to content

Repository files navigation

go-gqlcodegen

CI License: MIT

A native Go port of GraphQL Code Generator (@graphql-codegen/cli + core + the flagship TypeScript plugins). It reads your existing codegen.yml and produces byte-identical TypeScript output from a single static binary — no Node, no npm dependency graph, ~9ms cold start.

gqlcodegen generate          # reads codegen.yml, writes your types
gqlcodegen generate -c custom.yml
gqlcodegen init              # scaffold a codegen.yml

Install

# From source
go install github.com/jclyons52/go-gqlcodegen/cmd/gqlcodegen@latest

# Or download a prebuilt binary for your platform from the
# GitHub Releases page (Linux, macOS, Windows; amd64 + arm64).

Why

The Node CLI spends ~700ms before doing any work (boot + loading a ~400-module dependency graph). gqlcodegen is a single binary that generates the same output in ~9ms — a ~90× speedup that makes codegen viable in commit hooks and watch loops.

@graphql-codegen/cli (Node) gqlcodegen (Go)
cold start, small schema ~730ms ~28ms
161-type schema → 3,168-line file ~765ms ~28ms
real-world repo (libplanet explorer, 1,830-line schema, client preset) 805ms 8.9ms

Measured with /usr/bin/time on the parity fixture (see scripts/parity/) and, for the last row, by dogfooding the real planetarium/libplanet-explorer-frontend repo with its codegen.yml used unchanged — all 4 generated files byte-identical, 91× faster. The tool's own parity harness reproduces this continuously (see below).

Ported surface

Layer What works
Host config discovery (codegen.yml, codegen.json, package.json#codegen), env interpolation, YAML/JSON config, generates fan-out, glob schema/document loading, .graphql/.gql parsing, gql-tag pluck from code files, output writing (hash-skip, check mode, stale-file removal), lifecycle hooks, watch mode
Core plugin-chain orchestration, prepend/append hoisting, document validation, union/interface resolution
Plugins typescript, typescript-operations, typescript-resolvers, add, schema-ast, typed-document-node, gen-dts (gql-tag-operations), fragment-masking — all byte-identical to upstream output on the parity fixture
Presets client — fans one generates entry into graphql.ts, gql.ts, fragment-masking.ts, index.ts, all byte-identical to upstream

Verification: scripts/parity/parity.sh runs the real @graphql-codegen/cli and this binary on the same fixture and diffs every generated file. All four configs (typescript, +operations, +resolvers, client preset) are byte-identical, including a 161-type stress schema.

Real-world dogfood

go-gqlcodegen was validated against planetarium/libplanet-explorer-frontend — a production GraphQL client with a 1,830-line schema (12 custom scalars, 8 enums, 11 @deprecated fields, custom root types) and 10 queries + 2 fragments:

  • Config unchanged: its codegen.yml (preset: client + a BigInt scalar mapping) was used as-is.
  • Byte-identical output: all 4 generated files (graphql.ts, gql.ts, fragment-masking.ts, index.ts) match current @graphql-codegen byte-for-byte, and the generated files typecheck under the repo's strict tsconfig.
  • Performance: 805ms (Node) → 8.9ms (Go) — 91× faster.

The dogfooding surfaced three parity gaps the synthetic fixture missed (document-definition ordering, unknown short-circuit in nullable variables, and the graphql-js 80-char argument-printing rule) — all fixed and covered by the parity harness.

Not ported in v1

  • .ts/.js/.mts config files — the Node ecosystem evaluates these via jiti. v1 supports YAML + JSON and rejects TS/JS configs with a clear error. A tsgo-based loader is planned (the tsgo binary is already vendored in the sibling ts-go-morph project).
  • near-operation-file preset — planned; requires a per-document output splitter and would share the ClientSideBaseVisitor machinery already ported for the client preset.
  • The 100+ ecosystem plugins (typescript-react-apollo, etc.) — plugins are compiled into the binary via a registry. The Plugin interface is kept RPC-friendly so hashicorp/go-plugin can be added for third-party plugins.
  • .graphqlrc multi-project (D8) and URL / introspection-JSON schema loading (D9) — later phases. SDL file/glob schemas are fully supported.

Formatting note

The Node pipeline runs Prettier over generated output. Go has no Prettier, so gqlcodegen uses a hand-rolled deterministic code writer. Output is byte-identical to the Node pipeline for the supported surface (verified by the parity harness) — the hand-rolled writer was tuned to match, not just approximate, upstream's output.

Plugin registry

Plugins register themselves via init() (see internal/plugins/). To add a new plugin:

package myplugin

import "github.com/jclyons52/go-gqlcodegen/internal/plugin"

type Plugin struct{}

func init() { plugin.Register(Plugin{}) }

func (Plugin) Name() string                 { return "my-plugin" }
func (Plugin) Generate(...) (*plugin.Output, error) { /* ... */ }

then blank-import it in internal/registry/registry.go.

Development

go build ./cmd/gqlcodegen        # build the binary
go test ./...                    # unit + golden tests
go test -race ./...              # race-detector pass
scripts/parity/parity.sh         # byte-diff against the real tool (needs node)

Releases are cut by tagging a version (git tag v0.2.0 && git push --tags); GitHub Actions builds cross-platform binaries with goreleaser and attaches them to the release. See .goreleaser.yaml and .github/workflows/release.yml.

Layout: cmd/gqlcodegen (CLI), internal/config (config loading), internal/load (schema/document loading), internal/core (plugin orchestration), internal/visitor (shared code writer / naming / scalars), internal/plugins/* (the ported plugins). Full design + phase history in PLAN.md.

About

Native Go port of GraphQL Code Generator — byte-identical TypeScript output, ~90x faster cold start, no Node runtime

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages