Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

onlyOperationTypes to include only used enum types #10232

Open
ThePlenkov opened this issue Jan 8, 2025 · 7 comments
Open

onlyOperationTypes to include only used enum types #10232

ThePlenkov opened this issue Jan 8, 2025 · 7 comments

Comments

@ThePlenkov
Copy link

Is your feature request related to a problem? Please describe.

So I want to build an SDK for Gitlab graphql API. I only have few queries - for that purpose I've created them as separate graphql documents and I want to generate getSDK with few methods.
By default my SDK contains 50K lines, with onlyOperationTypes it gets reduced to 13K, however, if I check generated sdk.ts i have many enums which are not in use in any of types therefore it's just meaningless to generated them.

Describe the solution you'd like

As a proposal we may introduce a feature like dedupeEnums which should remove not used enums, it may be default to true if onlyOperationTypes is true.

What do you think about this proposal?

Describe alternatives you've considered

No response

Any additional important details?

No response

@eddeee888
Copy link
Collaborator

Hi @ThePlenkov , you mentioned in #10233 you can use a tool to remove unused export? Could you do that for unused enums?

@ThePlenkov
Copy link
Author

ThePlenkov commented Jan 9, 2025

that's correct! I found it later, after this issue created. But it appeared later that i don't even need knip. Generating enums as union types + noExport + bundilng your project will do a tree shaking and only generate required types. However in this case we need to extract types again from the only exported type getSdk, so it's not that much convenient.

Additionally, if we want to use real enums, then even not being exported - it will be still generated in the bundle as a variable ( at least my rollup configuration do this ) So it's not in d.ts file but still make bundle size huge. ( 300KB -> 1KB )

So which logic I would apply porgrammatically:

  • build graphql SDK without export ( typescript + operations + sdk )
  • Apply this eslint rule recursively until all unused vars are removed ( it will remove not only enums, but also unsued types )
  • Transform typescript back to export everything

In this case we'll just give the perfect ts file to the bundler. I tried this concept manually - it works, but of course doesn't fit for automated development

image

@ThePlenkov
Copy link
Author

Hi @ThePlenkov , you mentioned in #10233 you can use a tool to remove unused export? Could you do that for unused enums?

so knip.dev only removes exports for enums, but doesn't remove enums themselves. It doesn't support unused variables or type properties yet.

Otherwise it would be very helpful here indeed

@ThePlenkov
Copy link
Author

Actually today I already found another principally different way. Instead of typescript manipulating - I thought - what if we just manipulate only graphql schema. So I found other tools from the guild: filtering and cleanup transformations.

This code seems to be working exactly how I want:

import { describe, it } from 'node:test';
import * as assert from 'node:assert/strict';
import { GraphQLSchema, printSchema } from 'graphql';
import { wrapSchema, FilterObjectFields } from '@graphql-tools/wrap';
import { pruneSchema } from '@graphql-tools/utils';
import { makeExecutableSchema } from '@graphql-tools/schema';

describe('GraphQL Schema Transformation with Enums', () => {
  it('should filter only the project query and prune unused types and enums', () => {
    // Original schema with enums
    const gitlabSchema: GraphQLSchema = makeExecutableSchema({
      typeDefs: `
        type Query {
          project(fullPath: ID!): Project
          user(id: ID!): User
          issues(projectId: ID!): [Issue]
        }

        type Project {
          id: ID
          name: String
          visibility: Visibility
        }

        type User {
          id: ID
          name: String
          role: UserRole
        }

        type Issue {
          id: ID
          title: String
          state: IssueState
        }

        enum Visibility {
          PRIVATE
          PUBLIC
        }

        enum UserRole {
          ADMIN
          DEVELOPER
          GUEST
        }

        enum IssueState {
          OPEN
          CLOSED
        }
      `,
    });

    // Transform schema: Keep only the `project` field in `Query`
    const filteredSchema: GraphQLSchema = wrapSchema({
      schema: gitlabSchema,
      transforms: [
        new FilterObjectFields((typeName, fieldName) => {
          // Filter only the `project` field in the Query type
          console.log(`Filtering: ${typeName}.${fieldName}`);
          if (typeName === 'Query') {
            return ['project'].includes(fieldName);
          }
          return true;
        }),
      ],
    });

    // Prune unused types and enums
    const prunedSchema: GraphQLSchema = pruneSchema(filteredSchema);

    // Final schema as string
    const resultSchema: string = printSchema(prunedSchema);

    // Expected schema as string
    const expectedSchema: string = `
type Query {
  project(fullPath: ID!): Project
}

type Project {
  id: ID
  name: String
  visibility: Visibility
}

enum Visibility {
  PRIVATE
  PUBLIC
}
`.trim();

    // Assert the transformed schema matches the expected schema
    assert.equal(resultSchema, expectedSchema);
  });
});

So it filters out unnecessary query types and this triggers pruning first types, then enums

Exactly what I want - feeding this schema to codegen would solve the issue.

Now new problem is - i'd like still to use codegen. I see schema is required to be URL or path, so I cannot provide GraphQLSchema "as is"

Do you know may be some recommeded ways to transform schema on the fly? @eddeee888

Thanks!

@eddeee888
Copy link
Collaborator

Awesome! Glad you found the tools to do it! To use the pruned schema, maybe you could:

  • Extend the prune script to use fs.write to write the schema to a file e.g. schema.pruned.graphql
  • Update codegen.ts to point to this file e.g. schema: './schema.pruned.graphql'
  • Then codegen scripts in package.json to run prune, before codegen?

@ThePlenkov
Copy link
Author

Actually I was able to debug and file that schema : { loader: './my-schema-loader.ts } } is also a valid config option - so I can write a custom logic in that loader and do schema transformation. However this feature is mainly about the way to enforce this pruning during the generation. That's how we don't even generate those enums which are not part of the operations. Do you think that's feasible to extend the meaninf of only OperationTypes?

I was also disappointed to find out - that this magic "Delete all unused declarations" button in VS Code - is a very unique IDE feature and do not have some reusable library to repeat this.. Still searching for a right way to prune ts document.

As what I mentioned - unfortunately noExport comes with a side effect that no types are exported at all

@eddeee888
Copy link
Collaborator

I see!
I think it could be done by codegen, however it sounds like extra analysis need to be done by codegen to remove the types.
This use case is fairly specific as well so I'd recommend doing this on your end first if you are blocked - either using your method or using TypeSript AST to programmatically remove unused types. 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants