Based on the upvotes/comments on that original issue, it seems to be a relatively common problem in the community, so I would like to propose an actionable solution (which I can tentatively plan to implement if it's accepted).
gqlgen already splits up the templates for the generated code into args.gotpl, directives.gotpl, field.gotpl, generated!.gotpl, input.gotpl, interface.gotpl, object.gotpl, and type.gotpl. The code generation process essentially generates code based on each of these templates, adds a header, and concatenates the results into the file specified by templates.Options.Filename.
I propose that templates.Options be updated to support passing a directory into Filename. If Filename is a single file, behavior would be unchanged. If Filename is a directory, it would output files for each template as separate files.
For instance, the following gqlgen.yaml
...
exec:
package: schema
filename: ./generated/
...
would yield the file structure:
generated/
args.generated.go
directives.generated.go
field.generated.go
generated!.generated.go
input.generated.go
interface.generated.go
object.generated.go
type.generated.go
The header for each *.generated.go would follow the same format as the single output file's header, so each output file would look like:
[If cfg.GeneratedHeader] // Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
[cfg.PackageDoc]
package [cfg.PackageName]
[cfg.FileNotice]
import (
...
)
[Code generated by `*.gotpl` template for this region]
In templates.Render(), update the loop over roots to write to its own file, rather than a single buffer. Also move the header lines into the loop so they get added to each file.
templates.Options.Template is specified and templates.Options.Filename is a directory?The most straightforward approach would probably be to create:
generated/
<template name>.generated.go
But open to any other suggestions here.
templates.Options.RegionTags be if templates.Options.Filename is a directory?Currently, when templates.Options.Template is set, RegionTags is still used, and just encloses that single template's generated code in region tags. To mimic this behavior, it'd probably make sense to enclose each *.generated.go file contents in region tags. However this doesn't seem to be particularly useful, so the alternative would be to simply ignore RegionTags when templates.Options.Filename is set to a directory.
If splitting up the output based on the current .gotpl templates still yields files which are too large, we could take this one step further and reorganize the templates to be based on operation type rather than declaration type.
For example, given the following schema:
extend type Query {
foo(id: ID): Foo
}
extend type Mutation {
bar(id: ID): Bar
}
it could generate
generated/
foo.generated.go
bar.generated.go
common.generated.go
where each generated file would contain all the declarations relating to that operation in the schema (plus some common.generated.go with any base declarations that might be needed which aren't specific to an individual operation). I'd be interested to gauge interest in this, but would probably leave implementation of it to a follow-up proposal.
Quick update here: I discussed this proposal with @vektah last week and we revised the proposed output from following the region tags in the existing generated.go to following the schema (similar to the layout: follow-schema supported for resolvergen). So if your schema looks like
schema/
common.graphql
user.graphql
settings.graphql
onboarding_flow.graphql
then the output would be
generated/
common.generated.go
user.generated.go
settings.generated.go
onboarding_flow.generated.go
This would have a few benefits over the original proposal:
field.generated.go would still be quite large. With this new proposal, if any generated file in your project gets too big, you can simply split up the corresponding schema into multiple files.Hoping to have a draft of this implemented sometime in the next few weeks, but feel free to comment here with any thoughts on this revision.
@kevinmbeaulieu Hey Kevin, I follow your instruction for split the file,
here are my structure and config
- graph/
- document.graphql
- schema.graphql
- pkg/
- resolver/
- resolver.go
in document.graphql :
extend type Mutation {
changeAccessLevelDocument(documentID: String!): Boolean! @isAuthenticated(for: ADMIN)
}
and my config like this:
schema:
- graph/*.graphql
exec:
filename: pkg/resolver/generated.go
package: resolver
resolver:
layout: follow-schema
dir: pkg/resolver
package: resolver
filename_template: "{name}.resolvers.go"
and i have custom generate also for mutating hook with a filename called generate_hook.go:
package main
import (
"fmt"
"github.com/99designs/gqlgen/api"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin/modelgen"
"os"
"strings"
)
func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild {
for _, model := range b.Models {
for _, field := range model.Fields {
name := field.Name
if name == "id" {
name = "_id,omitempty"
omit := strings.TrimSuffix(field.Tag, `"`)
field.Tag = fmt.Sprintf(`%v,omitempty"`, omit)
}
field.Tag += ` bson:"` + name + `"`
}
}
return b
}
func main() {
cfg, err := config.LoadConfigFromDefaultLocations()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
os.Exit(2)
}
p := modelgen.Plugin{
MutateHook: mutateHook,
}
err = api.Generate(cfg,
api.NoPlugins(),
api.AddPlugin(&p),
)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err.Error())
os.Exit(3)
}
}
after I run go run generate_hook.go
it doesn't generate files like what u said, and it just updated the code in pkg/resolver/generated.go in my project
am i wrong to use that??
@ibantoo The follow-schema layout for resolver should already exist, this issue is about extending that functionality to exec as well. So not sure what's going wrong with your experience, but might be worth filing a separate ticket. From a quick look your config looks right to me, but I'm somewhat new to this so might be missing something (only sanity checks I can think of would be to make sure you're on the newest version of gqlgen, because iirc follow-schema was added semi-recently
Most helpful comment
Quick update here: I discussed this proposal with @vektah last week and we revised the proposed output from following the region tags in the existing
generated.goto following the schema (similar to thelayout: follow-schemasupported for resolvergen). So if your schema looks likethen the output would be
This would have a few benefits over the original proposal:
field.generated.gowould still be quite large. With this new proposal, if any generated file in your project gets too big, you can simply split up the corresponding schema into multiple files.Hoping to have a draft of this implemented sometime in the next few weeks, but feel free to comment here with any thoughts on this revision.