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

Generation of Duplicated Fragments in nested fragments #3063

Closed
zerocity opened this issue Dec 2, 2019 · 12 comments
Closed

Generation of Duplicated Fragments in nested fragments #3063

zerocity opened this issue Dec 2, 2019 · 12 comments

Comments

@zerocity
Copy link

zerocity commented Dec 2, 2019

Hi , i get several errors of "There can be only one fragment named ..." on the Client Side(Apollo).

On this example below it would be "There can be only one fragment named uptimeMeasurements"

Problem

The Group will import "PublicUptimeMeasurementsFragmentDoc" and the Component which could be a child of the group.

The generated Fragments look like this

// Pseudo Interfaces  for clarification 
export type PublicComponentTreeFragment = {
  components: Array<ComponentGroup | Component>
}

export type ComponentGroup = {
  id: Scalars['ID']
  label: Scalars['String']
  components: Array<Component>
  uptimeMeasurements: UptimeAggregation
}

export type Component = {
  id: Scalars['ID']
  label: Scalars['String']
  uptimeMeasurements: UptimeAggregation
}

// generated fragments
export const PublicComponentTreeFragmentDoc = gql`
  fragment publicComponentTree on PublicRootComponentGroup {
    id
    label
    components {
      ...publicComponent
      ...publicComponentGroup
    }
  }
  ${PublicComponentFragmentDoc}
  ${PublicComponentGroupFragmentDoc}
`

export const PublicComponentFragmentDoc = gql`
  fragment publicComponent on PublicComponent {
    id
    label
    uptimeMeasurements(view: $componentTimespan) {
      ...publicUptimeMeasurements
    }
  }
  ${PublicUptimeMeasurementsFragmentDoc}
  ${PublicResponseTimeMetricFragmentDoc}
`
export const PublicComponentGroupFragmentDoc = gql`
  fragment publicComponentGroup on PublicComponentGroup {
    id
    label
    uptimeMeasurements(view: $componentTimespan) {
      ...publicUptimeMeasurements
    }
    components {
      ...publicComponent
    }
  }
  ${PublicUptimeMeasurementsFragmentDoc}
  ${PublicComponentFragmentDoc}
`

i am using version of 1.9.1

overwrite: true
schema: "./schema.graphql"
documents: 'src/**/*.gql'
generates:
  src/types.ts:
    plugins:
      - add: // tslint:disable
      - add: /* eslint-disable */
      - typescript
    config: 
      namingConvention:
        enumValues: change-case#upperCase
  src/:
    preset: near-operation-file
    presetConfig:
      extension: .gen.tsx
      baseTypesPath: types.ts
    plugins:
      - add: // tslint:disable
      - add: /* eslint-disable */
      - typescript-operations
      - typescript-react-apollo
    config:
      withHooks: true
      preResolveTypes: true
      skipTypename: true
      gqlImport: graphql-tag.macro

is there a option to import fragments on queryDocument level ?

thanks for help

@ardatan ardatan changed the title Generation of Dublicated Fragments in nested fragments Generation of Duplicated Fragments in nested fragments Dec 3, 2019
@lanwin
Copy link

lanwin commented Dec 3, 2019

I am suffering from the same problem and did not find a good solution yet. It seems to make no difference if I use graphql-tag.macro or graphql-tag.

@lanwin
Copy link

lanwin commented Dec 4, 2019

@zerocity seems to be a bug in graphql-tag. I added a pullrequest there apollographql/graphql-tag#278

@zerocity zerocity closed this as completed Dec 5, 2019
@sarink
Copy link

sarink commented Mar 23, 2020

Is there any workaround? This is completely blocking me

@lanwin
Copy link

lanwin commented Mar 23, 2020

Yes there is. Ive created a minimal graphql-tag version which outputs only the source of the query instead of the full documentNode. This version did not have this bug, works with graphqlgen and is available here https://gist.github.com/lanwin/bdc33d0bf291851bda48d17f98b98ff2

I did not expect the bug fixed in graphql-tag soon. It did not seems to be active at the moment.

@sarink
Copy link

sarink commented Mar 26, 2020

@lanwin are you by any chance using a custom tagName (something other than gql)? I noticed that when I switched back to gql, this problem suddenly vaporized. I switched back again (to confirm), and was indeed hit by this error again.

I'm communicating with multiple independent schemas, and wanted to use the eslint-graphql-plugin (which also supports changing the tagName), so my setup is a little unique.

Anyway, great work on this (it's sad that it won't get merged anytime soon), but thanks for sharing.

@lanwin
Copy link

lanwin commented Mar 29, 2020

tagName

@sarink no I still use gql as tag name and import from "./graphql-tag-source" instead.

And then Ive configured use config option to let the generator use "gqlImport: generated/graphql/graphql-tag-source" instead.

@narthur
Copy link

narthur commented Feb 1, 2021

I was encountering this error:

There can be only one fragment named "x".

@mattleff and I wrote this function and are using it in our fetch wrapper to fix the issue:

import { parse } from 'graphql';
import { print } from 'graphql/language/printer';

const removeDuplicateFragments = (query: string): string => {
	const ast = parse(query);

	const seen: string[] = [];

	const newDefinitions = ast.definitions.filter((def) => {
		if (def.kind !== 'FragmentDefinition') {
			return true;
		}

		const id = `${def.name.value}-${def.typeCondition.name.value}`;
		const haveSeen = seen.includes(id);

		seen.push(id);

		return !haveSeen;
	});

	const newAst = {
		...ast,
		definitions: newDefinitions,
	};

	return print(newAst);
};

@sparebytes
Copy link

sparebytes commented May 17, 2021

Thank you @narthur! Here is the same thing but for Apollo Client Links:

import { ApolloLink } from "@apollo/client";

/**
 * Problem: Sometimes Apollo Client includes the one fragment multiple times.
 * Workaround: Remove duplicate fragments from query definitions
 * @see https://github.com/dotansimha/graphql-code-generator/issues/3063#issuecomment-842536997
 */
export function makeFragmentDeDupeLink() {
  const fragmentDeDupeLink = new ApolloLink((operation, forward) => {
    const previousDefinitions = new Set<string>();
    const definitions = operation.query.definitions.filter((def) => {
      if (def.kind !== "FragmentDefinition") return true;
      const name = `${def.name.value}-${def.typeCondition.name.value}`;
      if (previousDefinitions.has(name)) {
        return false;
      }
      previousDefinitions.add(name);
      return true;
    });
    const newQuery = {
      ...operation.query,
      definitions,
    };
    operation.query = newQuery;
    return forward(operation);
  });
  return fragmentDeDupeLink;
}

@mbrowne
Copy link

mbrowne commented Jun 29, 2021

I was able to resolve this issue by enabling the dedupeFragments option of the typescript-operations plugin:
https://www.graphql-code-generator.com/docs/plugins/typescript-operations

config:
  dedupeFragments: true

@Dar0n
Copy link

Dar0n commented Oct 6, 2021

I was encountering this error:

There can be only one fragment named "x".

@mattleff and I wrote this function and are using it in our fetch wrapper to fix the issue:

import { parse } from 'graphql';
import { print } from 'graphql/language/printer';

const removeDuplicateFragments = (query: string): string => {
	const ast = parse(query);

	const seen: string[] = [];

	const newDefinitions = ast.definitions.filter((def) => {
		if (def.kind !== 'FragmentDefinition') {
			return true;
		}

		const id = `${def.name.value}-${def.typeCondition.name.value}`;
		const haveSeen = seen.includes(id);

		seen.push(id);

		return !haveSeen;
	});

	const newAst = {
		...ast,
		definitions: newDefinitions,
	};

	return print(newAst);
};

This solution works for me, but the standard dedupeFragments: true option from generator does not. Still getting fragments definitions copied to every query where they are used.
Any hints why that could be?

@LarsEjaas
Copy link

I was able to resolve this issue by enabling the dedupeFragments option of the typescript-operations plugin: https://www.graphql-code-generator.com/docs/plugins/typescript-operations

config:
  dedupeFragments: true

Thanks for this! Worked for me with named fragments 🙌🕺

@Marviel
Copy link

Marviel commented Jan 18, 2023

dedupeFragments didn't seem to work with me, but this is an expanded version of the above function, in context with the httplink provided.

Took me a bit to get that part put together, thought I'd leave it here for helpfulness.

YMMV!

import { createHttpLink } from "@apollo/client";
import { parse } from "graphql";
import { print } from "graphql/language/printer";

/**
 * This will remove duplicate fragments from the request body.
 * [originally written by @narthur](https://github.com/dotansimha/graphql-code-generator/issues/3063#issuecomment-771179054)
 * @param fullReqStr The request body.
 * @returns The request body with duplicate fragments removed.
 */
const removeDuplicateFragments = (fullReqStr: string): string => {
    try {
        const fullReqObject = JSON.parse(fullReqStr);

        const ast = parse(fullReqObject.query);

        const seen: string[] = [];

        const newDefinitions = ast.definitions.filter((def: any) => {
            if (def.kind !== "FragmentDefinition") {
                return true;
            }

            const id = `${def.name.value}-${def.typeCondition.name.value}`;
            const haveSeen = seen.includes(id);

            seen.push(id);

            return !haveSeen;
        });

        const newAst = {
            ...ast,
            definitions: newDefinitions,
        };

        return JSON.stringify({
            ...fullReqObject,
            query: print(newAst),
        });
    } catch (err: any) {
        console.log("err", err);
    }

    // If we somehow failed at our task, just return the original string.
    return fullReqStr;
};

export const createHTTPApolloLink = (uri: string) => {
    return createHttpLink({
        uri,
        fetch: async (uri, options) => {
            const fixedOptions = {
                ...options,
                body: options?.body ? removeDuplicateFragments(options?.body as string) : undefined
            };
            return fetch(uri, fixedOptions);
        },
    });
};

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

9 participants