Giter VIP home page Giter VIP logo

Comments (14)

ucw avatar ucw commented on August 25, 2024 3

For my project, I wrote a rollup plugin based on babel plugin that replaces the source code of graphql queries with generated code. This way tree-shaking graphql.ts is completely eliminated. It is also important to enable enumsAsTypes: true in the code generator settings

from graphql-code-generator.

CaptainN avatar CaptainN commented on August 25, 2024 1

Ah, thats interesting. We'd probably need to complicate the API to solve for that - like adding an additional method for registering fragments. But that's getting complicated:

const A = graphql.fragment(`
  fragment A on Query {
    a
  }
`)

const B = graphql.fragment(`
  fragment B on Query {
    ...A
  }
`, [A])

const Query = graphql(`
  query {
    ...A
    ...B
  }
`, [A, B])

// implementation (ignoring typescript validation for the moment)
const fragStore = [];
function getFragmentsFromDeps(deps: string[]) {
  return fragStore
      .filter(frag => deps.includes(frag.fragAndDeps))
      .map(frag => frag.fragment)
}

export function graphql(source: string, fragments: string[] = null) {
  if (Array.isArray(fragments) && fragments.length > 0) {
    const frags = getFragmentsFromDeps(fragments);
    // dedupe frags, and combine with source
    return [...new Set(frags), source].join("\n");
  } else {
    return source;
  }
}

graphql.fragments = (fragment: string, deps: string[] = null) => {
  const fragAndDeps = (Array.isArray(deps)) 
    ? [...new Set(getFragmentsFromDeps(deps)), fragment].join("\n")
    : fragment;
  fragStore.push({ fragment, deps, fragAndDeps });
  return fragAndDeps; // I assume this needs to return the aggregated string for type gen to work
}

I didn't test any of that (there may be some cases where we need to recurse on the deps array - I'm not sure, would need to write some unit tests), but the idea would be to store enough info about the registered fragment, that we can reconstruct the correct query package later.

I don't know what impact that would have on type generation side of things, but on the runtime this would work, because the package resolver would make sure to include all of the necessary linked fragments in the correct order, as long as they properly specified in the deps arrays.

It should be possible to support even trickier cases like:

const A = graphql(`
  fragment A on Query {
    a
  }
`)

const B = graphql(`
  fragment B on Query {
    ...A
  }
`, [A])

const Query = graphql(`
  query {
    ...B
  }
`, [B])

As far as typescript errors - yeah, that's an issue. We'd need some way to validate that, maybe an eslint rule? Again, adding more complexity...

from graphql-code-generator.

n1ru4l avatar n1ru4l commented on August 25, 2024

By doing this globally referenced fragments could no longer be used within the GraphQL operation strings.

from graphql-code-generator.

CaptainN avatar CaptainN commented on August 25, 2024

Hmm, I didn't consider fragments. Can you show me an example? I'd like to see if I can work out a pattern that would support that.

from graphql-code-generator.

n1ru4l avatar n1ru4l commented on August 25, 2024

@CaptainN

const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
...FilmItem
}
}
}
}
`);

from graphql-code-generator.

CaptainN avatar CaptainN commented on August 25, 2024

I added a global fragment to my playground, and ran the generator - it doesn't look like it does anything all that different from including any other query. It doesn't seem like anything else uses the documents object, which is the thing I want to get rid of. I think this would still work as expected:

import * as types from "./graphql";

type QueryMap = {
  "fragment UserFields on User { name }": typeof types.UserFieldsFragmentDoc
  "query GetUser($id: ID!) { user(id: $id) { ...UserFields } }": typeof types.GetUserDocument,
  "query GetUsername($id: ID!) { user(id: $id) { username } }": typeof types.GetUsernameDocument,
};

// Overloaded function signatures
export function graphql<TSource extends keyof QueryMap>(source: TSource): QueryMap[TSource];
export function graphql(source: string): unknown;

// Function implementation
export function graphql(source: string) {
  return source;
}

Am I missing some other type of output?

from graphql-code-generator.

n1ru4l avatar n1ru4l commented on August 25, 2024

Yeah, if the graphql function only returns the parameter passed to it:

export function graphql(source: string) {
  return source;
}

That string does not contain the fragment definition, thus when the string is sent to the server, the GraphQL execution will fail.

In addition to that, features like persisted documents will no longer work without the lookup map/babel/swc plugin.

from graphql-code-generator.

CaptainN avatar CaptainN commented on August 25, 2024

Got it. So you still need some kind of runtime lookup system, at least for fragments.

it's just that the map is huge, and the babel/swc plugins either don't work at all (swc) in nextjs, or don't seem to do much even if you get it to work (babel) in nextjs

(And SWC produces smaller chunks - so you actually overall lose budget if you use babel vs swc in next.js - even if I can get the graphql map size down to essentially 0)

from graphql-code-generator.

n1ru4l avatar n1ru4l commented on August 25, 2024

The Babel plugin works perfectly for us. SWC has known issues.

We are happy for suggestions on alternative approaches, but right now I am afraid the lookup map (by default) is the only solution that does not force people to use the plugins.

from graphql-code-generator.

CaptainN avatar CaptainN commented on August 25, 2024

Heck, I'd be happy with a solution that does what I've proposed, maybe hidden behind a setting in codegen.ts, even if it breaks global Fragments.

To me, this isn't terrible:

import { graphql } from "data/types/gql";
import ReportFieldsFragment from "data/fragments/ReportFieldFragment";

export default graphql(`
  ${ReportFieldsFragment}
  query reports($order: [Core_EditionSortInput!]) {
    core_reports(order: $order) {
      ...ReportFields
    }
  }
`);

I don't use persisted queries (without APQ anyway), so that's fine.

from graphql-code-generator.

n1ru4l avatar n1ru4l commented on August 25, 2024

We could consider passing the fragments as an array parameter to the graphql function, then the graphql function could just concatenate the strings. πŸ€”

export default graphql(`
  query reports($order: [Core_EditionSortInput!]) {
    core_reports(order: $order) {
      ...ReportFields
    }
  }
`, [ReportFieldsFragment]);

But, this could then cause issues where the same fragment is printed twice in the operation sent to the server because it was referenced within two or more fragments referenced by a query/mutation/subscription operation.
That would open a whole new deduplication rabbit-hole.

from graphql-code-generator.

CaptainN avatar CaptainN commented on August 25, 2024

You could dedupe that array pretty easily. What I'm wondering is if there is some pattern on the other side (in gql.ts) we could use to insert the same (deduped) array, without too much overhead. Even something like a hybrid solution, where global fragments are defined the old way, and queries are defined the new way. I'll play with it more later.

from graphql-code-generator.

CaptainN avatar CaptainN commented on August 25, 2024

Had to teach some karate classes, I'm back. I actually think your array solution would work nicely. There shouldn't be deduplication issues - if the Fragment is in the array once, it would only be included in the query package once. Should be pretty straight forward. Similar to the way I did it, without the use of template strings.

Function Implementation:

export function graphql(source: string, fragments: string[] = null) {
  if (Array.isArray(fragments) && fragments.length > 0) {
    return [...fragments, source].join("\n");
  } else {
    return source;
  }
}

from graphql-code-generator.

n1ru4l avatar n1ru4l commented on August 25, 2024

@CaptainN this solution won't work if a fragment is referenced by multiple fragments. You will end up with duplicated fragment definitions in the document string, which then will be rejected by the GraphQL server.

const A = graphql(`
  fragment A on Query {
    a
  }
`)

const B = graphql(`
  fragment B on Query {
    ...A
  }
`, [A])

const Query = graphql(`
  query {
    ...A
    ...B
  }
`, [A, B])

Also, you will get no typescript compiler/codegen error if a fragment was not passed, but is required as it is used within that operation document.

from graphql-code-generator.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.