-
-
Notifications
You must be signed in to change notification settings - Fork 454
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
Document GCG Codegen for GraphQL operation and fragment tags #1221
Comments
We have some progress on generating inline types: dotansimha/graphql-code-generator#6267 |
The gql-tag-operations preset is now available: https://www.graphql-code-generator.com/docs/presets/gql-tag-operations Also started a fragment type masking experiment: dotansimha/graphql-code-generator#6442 |
Such a good thingy this! Thanks so much for the hard yards @n1ru4l 👌🏻 |
@JoviDeCroock Should be possible with module augmentation/declaration merging: I quickly generated the contents of the /* eslint-disable */
import * as graphql from './graphql';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
const documents = {
'\n query Foo {\n Tweets {\n id\n }\n }\n': graphql.FooDocument,
'\n fragment Lel on Tweet {\n id\n body\n }\n': graphql.LelFragmentDoc,
'\n query Bar {\n Tweets {\n ...Lel\n }\n }\n': graphql.BarDocument,
};
// export function gql(source: string): unknown;
// export function gql(source: string) {
// return (documents as any)[source] ?? {};
// }
export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode<
infer TType,
any
>
? TType
: never;
declare module '@urql/core' {
export function gql(
source: '\n query Foo {\n Tweets {\n id\n }\n }\n'
): typeof documents['\n query Foo {\n Tweets {\n id\n }\n }\n'];
export function gql(
source: '\n fragment Lel on Tweet {\n id\n body\n }\n'
): typeof documents['\n fragment Lel on Tweet {\n id\n body\n }\n'];
export function gql(
source: '\n query Bar {\n Tweets {\n ...Lel\n }\n }\n'
): typeof documents['\n query Bar {\n Tweets {\n ...Lel\n }\n }\n'];
} Which worked instantly /* eslint-disable @typescript-eslint/no-unused-vars */
import { gql } from "urql"
import { DocumentType } from '../gql';
const FooQuery = gql(/* GraphQL */ `
query Foo {
Tweets {
id
}
}
`);
const LelFragment = gql(/* GraphQL */ `
fragment Lel on Tweet {
id
body
}
`);
const BarQuery = gql(/* GraphQL */ `
query Bar {
Tweets {
...Lel
}
}
`);
const doSth = (params: { lel: DocumentType<typeof LelFragment> }) => {
params.lel.id;
}; |
@JoviDeCroock I adjusted the preset! Shoutouts to @PabloSzx for helping me with this. 🚀 It is now possible to provide an ./dev-test/gql-tag-operations-urql/gql:
schema: ./dev-test/gql-tag-operations-urql/schema.graphql
documents: './dev-test/gql-tag-operations-urql/src/**/*.ts'
preset: gql-tag-operations-preset
presetConfig:
augmentedModuleName: '@urql/core' That allows writing this code: /* eslint-disable @typescript-eslint/no-unused-vars */
import { gql } from 'urql';
const FooQuery = gql(/* GraphQL */ `
query Foo {
Tweets {
id
}
}
`);
const LelFragment = gql(/* GraphQL */ `
fragment Lel on Tweet {
id
body
}
`);
const BarQuery = gql(/* GraphQL */ `
query Bar {
Tweets {
...Lel
}
}
`); Which will generate an /* eslint-disable */
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
declare module '@urql/core' {
export function gql(
source: '\n query Foo {\n Tweets {\n id\n }\n }\n'
): typeof import('./graphql').FooDocument;
export function gql(
source: '\n fragment Lel on Tweet {\n id\n body\n }\n'
): typeof import('./graphql').LelFragmentDoc;
export function gql(
source: '\n query Bar {\n Tweets {\n ...Lel\n }\n }\n'
): typeof import('./graphql').BarDocument;
export function gql(source: string): unknown;
export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode<
infer TType,
any
>
? TType
: never;
} See n1ru4l/character-overlay#339 for a usage example in a small app. Full PR is over here: dotansimha/graphql-code-generator#6492 This approach does not increase the application bundle size as all the codegen artifacts are only used for module augmentation during development 🥳 EDIT: This still would require minor adjustment within urql to work. The We can infer type: const CharacterQuery = gql(/* GraphQL */ `
query CharacterQuery($characterId: ID!) @live {
character(id: $characterId) {
id
...CharacterViewFragment
}
}
`); We cannot infer the type :( const CharacterQuery = gql(/* GraphQL */ `
query CharacterQuery($characterId: ID!) @live {
character(id: $characterId) {
id
...CharacterViewFragment
}
}
${CharacterViewFragment}
`); Maybe there might be some hacks that are possible. I tried altering the existing gql function, to have a global cache from where it tries to get the fragments and add them to the parsed document. However, this is flawed in scenarios where a fragment has not been registered yet (e.g. lazy-loaded code 🤔 ). const globalCache = new Map();
function gql() {
var e = arguments;
var n = new Map;
var a = [];
var o = [];
var i = Array.isArray(arguments[0]) ? arguments[0][0] : arguments[0] || "";
for (var u = 1; u < arguments.length; u++) {
var c = e[u];
if (c && c.definitions) {
o.push.apply(o, c.definitions);
} else {
i += c;
}
i += e[0][u];
}
applyDefinitions(n, a, t(i).definitions);
applyDefinitions(n, a, o);
const document = t({
kind: r.DOCUMENT,
definitions: a
});
// I added this block
if (document.definitions[0].kind === "FragmentDefinition") {
console.log("register fragment", document.definitions[0].name.value)
globalCache.set(document.definitions[0].name.value, document.definitions[0])
} else {
const visit = (selectionSet) => {
for (const selection of selectionSet.selections) {
if ((selection.kind === "Field" || selection.kind === "InlineFragment") && selection.selectionSet !== undefined) {
visit(selection.selectionSet)
} else if (selection.kind === "FragmentSpread") {
console.log("add fragment", selection.name.value, globalCache.get(selection.name.value))
const fragmentDefinition = globalCache.get(selection.name.value)
document.definitions.push(fragmentDefinition)
visit(fragmentDefinition.selectionSet)
}
}
}
visit(document.definitions[0].selectionSet)
}
return document
} Another possible solution would be the usage of a babel plugin, that fixes this during transpilation 🤔 |
I madesome progress on a babel plugin that rewrites The usage flow is the following:
References Example babel plugin usage setup with vitejs: n1ru4l/character-overlay@52a4c8e#diff-6a3b01ba97829c9566ef2d8dc466ffcffb4bdac08706d3d6319e42e0aa6890ddR29-R57 |
Up next: a data/fragment matching support for this codegen preset ;) dotansimha/graphql-code-generator#6442 |
Coming back to this we can leverage the |
Summary
This is connected to #901 in that we'd like to expand what is done with GraphQL Code Generator.
The basic idea is to have code generation that can keep up with Relay so that we enable and encourage better fragment best practices and enable a better control of the operation/fragment pipeline in the future. For now the goal is to be able to write queries with
gql
tags, interpolate fragments into them from other files (also written withgql
tags), and to expose allgql
tags A) with types somehow, and B) have separate__generated
files with a large combined operation.This encourages to define data requirements in the form of fragments in several places, and it actually creates a very compelling end to end experience with urql, where it becomes more Relay/Framework-like and encourages that paradigm. (Together with #964 this will be really strong)
Proposed Solution
TBD (This is still a placeholder and no proposed implementation path exists yet)
Requirements
TBD
cc @JoviDeCroock @dotansimha
The text was updated successfully, but these errors were encountered: