manifold-systems / manifold

Manifold is a Java compiler plugin, its features include Metaprogramming, Properties, Extension Methods, Operator Overloading, Templates, a Preprocessor, and more.
http://manifold.systems/
Apache License 2.0
2.43k stars 125 forks source link

GraphQL classes always generated even when unused #522

Closed skevy closed 1 month ago

skevy commented 1 year ago

Manifold is super cool! I'm investigating it for a project to help with GraphQL code generation. We have a very large schema -- 20k types, hundreds of thousands of fields. Reading through the docs, one of the things that excited me the most about Manifold was:

By default, Manifold compiles resource types to disk as the Java compiler encounters them in your code. As a consequence, a resource that is never used in your code as a type is not compiled.

However, I pulled down the "manifold-sample-graphql-app" project to try it out, and I added a new GraphQL type and compiled. This type is completely independent from the schema, is never used in code, but in target/classes/... I still see it being generated. I tried the same exercise with a JSON file (and added the json manifold plugin), and it correctly does not generate a class unless I use it, matching the experience in the docs.

Am I missing something? Is there something about GraphQL that doesn't allow for the "selective generation" behavior?

rsmckinney commented 1 year ago

Hi @skevy. Because graphql schemas can span multiple .gql files, the granularity of type generation is at the schema level for the GraphQL manifold. As such, all the files that comprise a single schema are gathered and processed to compile the schema type.

This is definitely a design decision on my part. I considered an alternative approach where schema components could compiled separately, but it was more convenient to package everything as one unit.

If your schema were divided into sub-schemas, you could leverage manifold's multiple schema feature using .graphqlconfig files. I've attached some javadoc that explains it a bit.

Hope this helps.

/**
 * This class reflects the <i>.graphqlconfig</i> file, which defines the scope of a GraphQL schema in terms of where
 * the .graphql resources may be located.
 * <p/>
 * When no .graphqlconfig files are present, there can be only one GraphQL schema definition and the entire module's
 * namespace is the schema's scope -- all the module's .graphql resource files are considered part of the schema's
 * scope.
 * <p/>
 * When using multiple .grpahqlconfig files, it is advisable to place them in separate, unrelated directories alongside
 * the schema files. This way, each schema is scoped to the entire directory and its subdirectories.
 * <p/>
 * Note, no two .graphqlconfig files may overlap in terms of resource scoping.
 * @see <a href="https://jimkyndemeyer.github.io/js-graphql-intellij-plugin/docs/developer-guide#setting-up-multi-schema-projects-using-graphql-config">Setting up Multi-schema Projects using graphql-config</a>
 */
class GqlScope
skevy commented 1 year ago

Ok yah this is helpful -- good to know it's a design decision. Likely, if we went through with using Manifold, we would implement our own Manifold plugin because we have a bunch of special needs in this area.

Can you point me to where in https://github.com/manifold-systems/manifold/blob/master/manifold-deps-parent/manifold-graphql/src/main/java/manifold/graphql/type/GqlManifold.java this type of decision is made? Admittedly I need to read more code but it wasn't quite clicking in my head how this was differing from the JSON plugin that did operate on a file-by-file basis.

rsmckinney commented 1 year ago

See usage of manifold.graphql.type.GqlScope#contains(file). For example, if you have two files movies.gql and queries.gql, although they compile as separate classes, the GqlScope includes both of them in the schema as the contains method indicates.

But... I wonder if manifold can improve on this. The inflection point is really at GqlParentType#addImports() where it calls importAllOtherGqlTypes(). A smarter approach would perform static analysis on the type and only include types that are necessary. Not too sure how I would go about performing that kind of analysis. I suppose a quick/dirty way would be to configure it. For instance, a config file that loosely defines discrete groupings of files.