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# 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).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).
| 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.
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+ aBigIntscalar mapping) was used as-is. - Byte-identical output: all 4 generated files (
graphql.ts,gql.ts,fragment-masking.ts,index.ts) match current@graphql-codegenbyte-for-byte, and the generated files typecheck under the repo's stricttsconfig. - 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.
.ts/.js/.mtsconfig files — the Node ecosystem evaluates these viajiti. v1 supports YAML + JSON and rejects TS/JS configs with a clear error. Atsgo-based loader is planned (thetsgobinary is already vendored in the siblingts-go-morphproject).near-operation-filepreset — planned; requires a per-document output splitter and would share theClientSideBaseVisitormachinery already ported for the client preset.- The 100+ ecosystem plugins (
typescript-react-apollo, etc.) — plugins are compiled into the binary via a registry. ThePlugininterface is kept RPC-friendly sohashicorp/go-plugincan be added for third-party plugins. .graphqlrcmulti-project (D8) and URL / introspection-JSON schema loading (D9) — later phases. SDL file/glob schemas are fully supported.
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.
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.
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.