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

hijack and make it work how I would imagine it #1

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
module.exports = {
extends: [
`@yarnpkg`,
],
};
module.exports = {};
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
.yarn/*
!.yarn/releases
.pnp.*
/lib
node_modules
1 change: 0 additions & 1 deletion .graphqlrc.yml

This file was deleted.

1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
demo/gql
9 changes: 2 additions & 7 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"eslint.nodePath": ".yarn/sdks"
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
55 changes: 0 additions & 55 deletions .yarn/releases/yarn-berry.cjs

This file was deleted.

1 change: 0 additions & 1 deletion .yarnrc.yml

This file was deleted.

30 changes: 19 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This package is a [preset](https://www.graphql-code-generator.com/docs/presets/p
I was a bit frustrated to have to import my typed GraphQL hooks from another file, and to leave my `gql` definitions dangling:

```ts
import {useFoo} from './generated-hooks';
import { useFoo } from "./generated-hooks";

gql`
query Foo {
Expand All @@ -27,14 +27,14 @@ gql`
So this tool lets you write the same thing, but in a single statement:

```ts
const {Foo} = gql(`#graphql
const FooQuery = gql(/* GraphQL */ `
query Foo {
bar
}
`);
```

I also didn't like that each operation had to be uniquely named not only within the same file, but also within the whole app - it felt like a step backward from encapsulation. This is addressed by hashing the operation names under the hood (cf the [generated output](https://github.com/arcanis/graphql-typescript-integration/blob/19a2e6b1f8949f12ab6e5120e002a30f79dbda41/demo/gql/index.ts#L4-L9)).
~~I also didn't like that each operation had to be uniquely named not only within the same file, but also within the whole app - it felt like a step backward from encapsulation. This is addressed by hashing the operation names under the hood (cf the [generated output](https://github.com/arcanis/graphql-typescript-integration/blob/19a2e6b1f8949f12ab6e5120e002a30f79dbda41/demo/gql/index.ts#L4-L9)).~~ For accessing fragments globally it is pretty handy to have global unique names. I like how relay does it.

Finally, I wanted something relatively easy to setup and maintain because I don't have that much time 😛

Expand Down Expand Up @@ -69,18 +69,26 @@ generates:
You can import the `gql` function from the generated folder. For instance, assuming your output folder is a subfolder named "gql":

```ts
import {gql} from './gql';
import { gql } from "./gql";

const {GetTweets, CreateTweet} = gql(`#graphql
const CreateTweet = gql(/* GraphQL */ `
query GetTweets {
Tweets {
id
}
}
`);

const FooFragment = gql(/* GraphQL */ `
fragment Foo on Tweet {
id
}
`);

const GetTweets = gql(/* GraphQL */ `
mutation CreateTweet {
CreateTweet(body: "Hello") {
id
...Foo
}
}
`);
Expand All @@ -96,15 +104,15 @@ This would let you do `import {gql} from '@app/gql'` from anywhere in your codeb

## Limitations

- You can't use fragments. I don't use them at the moment, so I haven't looked into them. However, given that queries are properly typed, you'll know when you're passing incomplete data around, which will let you add the missing fields to your queries. Not exactly like fragments, but still a decent workaround.
- ~~You can't use fragments. I don't use them at the moment, so I haven't looked into them. However, given that queries are properly typed, you'll know when you're passing incomplete data around, which will let you add the missing fields to your queries. Not exactly like fragments, but still a decent workaround.~~

- You must use the `const query = gql(...);` syntax (not the typical tagged template ``const query = gql`...`;`` one). This is because TypeScript isn't currently smart enough to forward tagged template parameters as their literal types (https://github.com/microsoft/TypeScript/issues/29432).
- You must use the `const query = gql(...);` syntax (not the typical tagged template `` const query = gql`...`; `` one). This is because TypeScript isn't currently smart enough to forward tagged template parameters as their literal types (https://github.com/microsoft/TypeScript/issues/29432).

- You should indicate `#graphql` right after opening the GraphQL source literal (right after the opening quote, before even any line return). This is because otherwise vscode-graphql won't recognize the string as being GraphQL and won't highlight it (they don't detect `gql` statements when used as regular function calls, unlike graphql-code-generator).
- ~~You should indicate `#graphql` right after opening the GraphQL source literal (right after the opening quote, before even any line return). This is because otherwise vscode-graphql won't recognize the string as being GraphQL and won't highlight it (they don't detect `gql` statements when used as regular function calls, unlike graphql-code-generator).~~ Fixed in upstream graphql-tools. You can no use `gql(/* GraphQL */ \`...\`)`

- A contrario, you must **not** use the `/* GraphQL */` magic comment inside the `gql(...)` function call. This is because [graphql-tag-pluck](https://www.graphql-tools.com/docs/graphql-tag-pluck/), the tool extracting these calls, has a bug and will register the document twice - which brings us to the final limitation:
- ~~A contraire, you must **not** use the `/* GraphQL */` magic comment inside the `gql(...)` function call. This is because [graphql-tag-pluck](https://www.graphql-tools.com/docs/graphql-tag-pluck/), the tool extracting these calls, has a bug and will register the document twice - which brings us to the final limitation:~~ Fixed :)

- You can only have a single `gql(...)` function call per file. This is because graphql-tag-pluck concatenates all documents from a single file into a single one, with no way to split them back. As a result we end up generating a `gql` overload for the composite query, instead of one for each individual document. It's however not a huge deal - just define multiple queries / mutations within the same `gql` call, and use destructuring to access them individually (cf example in the "Usage" section).
- ~~You can only have a single `gql(...)` function call per file. This is because graphql-tag-pluck concatenates all documents from a single file into a single one, with no way to split them back. As a result we end up generating a `gql` overload for the composite query, instead of one for each individual document. It's however not a huge deal - just define multiple queries / mutations within the same `gql` call, and use destructuring to access them individually (cf example in the "Usage" section).~~ You should now have one operation per gql call!

## License (MIT)

Expand Down
7 changes: 3 additions & 4 deletions codegen.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
schema: ./schema.graphql
documents: './demo/**/*.ts'
documents: "./demo/src/**/*.ts"

require:
- ts-node/register/transpile-only

pluckConfig:
skipIndent: true

generates:
./demo/gql/:
preset: graphql-typescript-integration
plugins: [graphql-typescript-integration/empty]
preset: ./lib/preset.js
plugins: [./lib/plugin.js]
21 changes: 13 additions & 8 deletions demo/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,28 +113,33 @@ export type User = {
avatar_url?: Maybe<Scalars['Url']>;
};

export type Foo_X9_QueryVariables = Exact<{ [key: string]: never; }>;
export type FooQueryVariables = Exact<{ [key: string]: never; }>;


export type Foo_X9_Query = (
export type FooQuery = (
{ __typename?: 'Query' }
& { Tweets?: Maybe<Array<Maybe<(
{ __typename?: 'Tweet' }
& Pick<Tweet, 'id'>
)>>> }
);

export type Bar_X9_QueryVariables = Exact<{ [key: string]: never; }>;
export type LelFragment = (
{ __typename?: 'Tweet' }
& Pick<Tweet, 'id' | 'body'>
);

export type BarQueryVariables = Exact<{ [key: string]: never; }>;


export type Bar_X9_Query = (
export type BarQuery = (
{ __typename?: 'Query' }
& { Tweets?: Maybe<Array<Maybe<(
{ __typename?: 'Tweet' }
& Pick<Tweet, 'id' | 'body'>
& LelFragment
)>>> }
);


export const Foo_X9_Document = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Foo_X9_"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"Tweets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<Foo_X9_Query, Foo_X9_QueryVariables>;
export const Bar_X9_Document = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Bar_X9_"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"Tweets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"body"}}]}}]}}]} as unknown as DocumentNode<Bar_X9_Query, Bar_X9_QueryVariables>;
export const LelFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Lel"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Tweet"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"body"}}]}}]} as unknown as DocumentNode<LelFragment, unknown>;
export const FooDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Foo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"Tweets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<FooQuery, FooQueryVariables>;
export const BarDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Bar"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"Tweets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Lel"}}]}}]}},...LelFragmentDoc.definitions]} as unknown as DocumentNode<BarQuery, BarQueryVariables>;
11 changes: 6 additions & 5 deletions demo/gql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
import * as graphql from './graphql';

const documents = {
"#graphql\n query Foo {\n Tweets {\n id\n }\n }\n\n query Bar {\n Tweets {\n id\n body\n }\n }\n": {
Foo: graphql.Foo_X9_Document,
Bar: graphql.Bar_X9_Document,
},
"\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: "#graphql\n query Foo {\n Tweets {\n id\n }\n }\n\n query Bar {\n Tweets {\n id\n body\n }\n }\n"): (typeof documents)["#graphql\n query Foo {\n Tweets {\n id\n }\n }\n\n query Bar {\n Tweets {\n id\n body\n }\n }\n"];
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"];

export function gql(source: string): unknown;
export function gql(source: string) {
Expand Down
18 changes: 0 additions & 18 deletions demo/sources/index.ts

This file was deleted.

26 changes: 26 additions & 0 deletions demo/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* eslint-disable @typescript-eslint/no-unused-vars */

import { gql } from "@app/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
}
}
`);
47 changes: 29 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,41 @@
"version": "1.0.5",
"main": "./sources/preset.ts",
"dependencies": {
"@graphql-codegen/add": "^2.0.2",
"@graphql-codegen/typed-document-node": "^1.18.9",
"@graphql-codegen/typescript": "^1.22.4",
"@graphql-codegen/typescript-operations": "^1.18.3",
"tslib": "^2.3.0"
"@graphql-codegen/add": "2.0.2",
"@graphql-codegen/typed-document-node": "1.18.9",
"@graphql-codegen/typescript": "1.22.4",
"@graphql-codegen/typescript-operations": "1.18.3",
"tslib": "2.3.0"
},
"devDependencies": {
"@app/gql": "link:./demo/gql",
"@graphql-codegen/cli": "^1.21.6",
"@graphql-codegen/plugin-helpers": "^1.18.7",
"@graphql-tools/utils": "^7.10.0",
"@graphql-typed-document-node/core": "^3.1.0",
"@rollup/plugin-typescript": "^8.2.1",
"@types/node": "^16.0.0",
"@yarnpkg/eslint-config": "^0.3.0-rc.6",
"eslint": "^7.30.0",
"graphql": "^15.5.1",
"rollup": "^2.52.7",
"ts-node": "^10.0.0",
"typescript": "^4.3.5"
"@graphql-codegen/cli": "1.21.6",
"@graphql-codegen/plugin-helpers": "1.18.7",
"@graphql-tools/utils": "8.0.0-alpha-f1d7b3c2.0",
"@graphql-typed-document-node/core": "3.1.0",
"@rollup/plugin-typescript": "8.2.1",
"@types/node": "16.0.0",
"@yarnpkg/eslint-config": "0.3.0-rc.6",
"eslint": "7.30.0",
"graphql": "15.5.1",
"patch-package": "6.4.7",
"prettier": "2.3.2",
"rollup": "2.52.7",
"ts-node": "10.0.0",
"typescript": "4.3.5"
},
"resolutions": {
"@graphql-tools/graphql-tag-pluck": "7.0.0-alpha-f1d7b3c2.0",
"@graphql-tools/code-file-loader": "6.3.2-alpha-f1d7b3c2.0",
"@graphql-tools/load": "6.2.9-alpha-f1d7b3c2.0",
"@graphql-tools/utils": "8.0.0-alpha-f1d7b3c2.0",
"@graphql-tools/merge": "6.2.15-alpha-f1d7b3c2.0",
"@graphql-tools/schema": "8.0.0-alpha-f1d7b3c2.0"
},
"scripts": {
"prepack": "rm -rf lib && rollup -c",
"postpack": "rm -rf lib"
"postpack": "rm -rf lib",
"postinstall": "patch-package && tsc"
},
"files": [
"lib",
Expand Down
28 changes: 28 additions & 0 deletions patches/@graphql-codegen+cli+1.21.6.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
diff --git a/node_modules/@graphql-codegen/cli/bin.js b/node_modules/@graphql-codegen/cli/bin.js
index 1307a11..7fdc41d 100755
--- a/node_modules/@graphql-codegen/cli/bin.js
+++ b/node_modules/@graphql-codegen/cli/bin.js
@@ -514,7 +514,13 @@ async function loadDocuments(documentPointers, config) {
const loaders = [new codeFileLoader.CodeFileLoader(), new gitLoader.GitLoader(), new githubLoader.GithubLoader(), new graphqlFileLoader.GraphQLFileLoader()];
const loadedFromToolkit = await load.loadDocuments(documentPointers, {
...defaultDocumentsLoadOptions,
- ignore: Object.keys(config.generates).map(p => path.join(process.cwd(), p)),
+ ignore: Object.keys(config.generates).map(p => {
+ let ignorePath = path.join(process.cwd(), p)
+ if (ignorePath.endsWith("**/*") === false) {
+ ignorePath += "**/*"
+ }
+ return ignorePath
+ }),
Comment on lines +9 to +16
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have an exact idea what is going on here and this patch probably breaks other stuff. I noticed that the following codegen config:

generates:
  ./demo/gql/:
    preset: ./lib/preset.js
    plugins: [./lib/plugin.js]

Would result in the following error:

Error: unable to find loader for glob "/Users/laurinquast/projects/graphql-typescript-integration/demo/gql/"
        at addGlobsToLoaders (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-tools/load/index.js
:295:19)
        at collectPathsFromGlobs (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-tools/load/inde
x.js:331:5)
        at collectSources (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-tools/load/index.js:21
6:23)
        at loadTypedefs (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-tools/load/index.js:609:
21)
        at loadDocuments (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-codegen/cli/bin.js:515:
31)
        at /Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-codegen/cli/bin.js:916:55
        at Task.task (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-codegen/cli/bin.js:761:17)
    Error: unable to find loader for glob "/Users/laurinquast/projects/graphql-typescript-integration/demo/gql/"
        at addGlobsToLoaders (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-tools/load/index.js
:295:19)
        at collectPathsFromGlobs (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-tools/load/inde
x.js:331:5)
        at collectSources (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-tools/load/index.js:21
6:23)
        at loadTypedefs (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-tools/load/index.js:609:
21)
        at loadDocuments (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-codegen/cli/bin.js:515:
31)
        at /Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-codegen/cli/bin.js:916:55
        at Task.task (/Users/laurinquast/projects/graphql-typescript-integration/node_modules/@graphql-codegen/cli/bin.js:761:17)

I dug into it and it seems that the folder is not properly ignored because the string must be a glob pattern. So I simply added it there.

loaders,
...config,
...config.config,
@@ -738,7 +744,7 @@ class CodegenContext {
const documents = await this._graphqlConfig.getProject(this._project).loadDocuments(pointer, config);
return documents;
}
- return loadDocuments(pointer, config);
+ return loadDocuments(pointer, config)
}
}
function ensureContext(input) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
diff --git a/node_modules/@graphql-tools/code-file-loader/index.js b/node_modules/@graphql-tools/code-file-loader/index.js
index 7f34ec1..9660b6d 100644
--- a/node_modules/@graphql-tools/code-file-loader/index.js
+++ b/node_modules/@graphql-tools/code-file-loader/index.js
@@ -244,8 +244,11 @@ class CodeFileLoader {
const content = await readFile(normalizedFilePath, { encoding: 'utf-8' });
const sources = await graphqlTagPluck.gqlPluckFromCodeString(normalizedFilePath, content, options.pluckConfig);
if (sources.length) {
+
+ const document = graphql.concatAST(sources.map(source => graphql.parse(source.body, options)))
return {
- document: graphql.concatAST(sources.map(source => graphql.parse(source, options))),
+ document,
+ rawSDL: sources.map(source => source.body).join(`\n#-#\n`),
Copy link
Owner Author

@n1ru4l n1ru4l Jul 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rawSDL is used for building the operation string to Type mapping. Since multiple gql calls are merged into a single document \n#-#\n is used for separating them later.

If we don't provide rawSDL at all, something very weird is happening as it is tried to be auto-generated somewhere and we end up with something weird similar to this: ardatan/graphql-tools#3124

For three gql calls within a single document rawSDL equals to the following string

[Object object]\n\n[Object object]\n\n[Object object]

location: pointer,
};
}
Loading