Gql Tag Operations Preset
Package name | Weekly Downloads | Version | License | Updated |
---|---|---|---|---|
@graphql-codegen/gql-tag-operations-preset | Oct 20th, 2022 |
Installation
yarn add -D @graphql-codegen/gql-tag-operations-preset
This preset generates typings for your inline gql
function usages, without having to manually specify import statements for the documents. All you need to do is import your gql
function and run codegen in watch mode.
Huge thanks to Maël Nison, who conceptualized the foundation for this preset over here.
import { gql } from './gql'
// TweetFragment is a fully typed document node
const TweetFragment = gql(/* GraphQL */ `
fragment TweetFragment on Tweet {
id
body
}
`)
const TweetsQueryWithFragment = gql(/* GraphQL */ `
query TweetsWithFragmentQuery {
Tweets {
id
...TweetFragment
}
}
`)
Getting Started
In order to use this preset, you need to add the following configuration to your codegen.yml
:
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'src/path/to/your/schema.graphql',
documents: ['src/**/*.ts', '!src/gql/**/*'],
generates: {
'./src/gql/': {
preset: 'gql-tag-operations-preset',
plugins: []
}
}
}
export default config
Now start your codegen in watch mode via yarn graphql-codegen --watch
.
Create a new file within your src
directory, e.g. ./src/index.ts
and add a query for your schema:
import { gql } from './gql'
// TweetsQuery is a fully typed document node!
const TweetsQuery = gql(/* GraphQL */ `
query TweetsQuery {
Tweets {
id
}
}
`)
Next we can simply add our GraphQL client of choice and use the typed document! Let's try urql
!
import { gql } from './gql'
import { useQuery } from 'urql'
// TweetsQuery is a fully typed document node/
const TweetsQuery = gql(/* GraphQL */ `
query TweetsQuery {
Tweets {
id
body
}
}
`)
const Tweets = () => {
const [result] = useQuery({ query: TweetsQuery })
const { data, fetching, error } = result
if (fetching) return <p>Loading…</p>
if (error) return <p>Oh no… {error.message}</p>
return (
<ul>
{/* data is fully typed 🎉 */}
{data.Tweets.map(tweet => (
<li key={tweet.id}>{tweet.body}</li>
))}
</ul>
)
}
If we want to use fragments, we can use some utilities for accessing the fragment types:
import { gql, DocumentType } from '../gql'
const TweetFragment = gql(/* GraphQL */ `
fragment TweetFragment on Tweet {
id
body
}
`)
const Tweet = (props: {
/* tweet property has the correct type 🎉 */
tweet: DocumentType<typeof TweetFragment>
}) => {
return <li data-id={props.tweet.id}>{props.tweet.body}</li>
}
const TweetsQuery = gql(/* GraphQL */ `
query TweetsQuery {
Tweets {
id
...TweetFragment
}
}
`)
const Tweets = () => {
const [result] = useQuery({ query: TweetsQuery })
const { data, fetching, error } = result
if (fetching) return <p>Loading…</p>
if (error) return <p>Oh no… {error.message}</p>
return (
<ul>
{data.Tweets.map(tweet => (
<Tweet key={tweet.id} tweet={tweet} />
))}
</ul>
)
}
You can find a full gql-tag-operations
example of this in the GraphQL Code Generator GitHub repository.
Improving bundle-size with the gql-tag-operations babel plugin
Because the generated code output is a single gql
function that looks similar to the following code, code-splitting and tree-shaking is not easily possible.
import * as graphql from './graphql'
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: '\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) {
// OH NO gql accesses the documents object, the bundler cannot make assumption over what is actually used at runtime :(
return (documents as any)[source] ?? {}
}
However, the @graphql-codegen/gql-tag-operations-preset
package ships with a babel plugin for rewriting the gql
usages to actual imports.
This is the code that you write:
import { gql, DocumentType } from '../gql'
const TweetFragment = gql(/* GraphQL */ `
fragment TweetFragment on Tweet {
id
body
}
`)
This is the code after the babel transformation:
import { TweetFragmentDoc } from './graphql'
const TweetFragment = TweetFragmentDoc
This will result in much smaller bundles when you code-split parts of your code.
Applying the babel plugin is straight-forward:
import { babelPlugin } from '@graphql-codegen/gql-tag-operations-preset'
export default {
plugins: [
// your other plugins such as typescript syntax stripping etc.
// make sure the artifactDirectory is the same as your generates path within the `codegen.yml`
[babelPlugin, { artifactDirectory: './src/gql' }]
]
}
Afterwards, you won't have to worry about bundle size anymore!
Using module augmentation for an existing gql
function from a library
Sometimes the library you are using, let's use urql
for this example, already exposes a gql
function and instead of generating a new one you would prefer to just generate types that extend the type-definitions of that gql
function.
This can be easily achieved by running the gql-tag-operations
plugin in module augmentation mode!
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'src/path/to/your/schema.graphql',
documents: ['src/**/*.ts', '!src/gql/**/*'],
generates: {
'src/gql/': {
preset: 'gql-tag-operations-preset',
plugins: [],
presetConfig: {
augmentedModuleName: '@urql/core'
}
}
}
}
export default config
When the augmentedModuleName
option is configured instead of the ./src/gql/index.ts
file a ./src/gql/index.d.ts
file is generated.
/* eslint-disable */
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
declare module '@urql/core' {
export function gql(
source: '\n query Bar {\n Tweets {\n ...Lel\n }\n }\n'
): typeof import('./graphql').BarDocument
export function gql(source: string): unknown
}
Now you can simply use your gql
function imported from @url/core
in your application code for having fully typed document nodes.
import { gql } from 'urql'
const FooQuery = gql(/* GraphQL */ `
query Foo {
Tweets {
id
}
}
`)
In case you are using fragments you MUST use the gql-tag-operations babel plugin as otherwise the gql
calls that
reference global fragments will cause runtime errors, as the gql
operation cannot find the global fragment.
Unfortunately, it is also NOT possible to embed fragments with template string interpolation, as it breaks
TypeScript type-inference.
import { gql } from 'urql'
// This causes an error without the babel plugin
const FooQuery = gql(/* GraphQL */ `
query Foo {
Tweets {
...SomeTweetFragment
}
}
`)
You can find a full gql-tag-operations-urql
example of this in the GraphQL Code Generator GitHub repository.
Fragment Masking
Fragment masking is a powerful tool that allows building scalable UI components, where each component only has access to the data dependencies described by its fragments. The fragments of those components are then composed on a query operation. This pattern is also known as data masking in the context of relay. gql-tag-operations-preset
allows using fragment masking with any GraphQL client.
Fragment masking is enabled via the fragmentMasking
config option.
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'src/path/to/your/schema.graphql',
documents: ['src/**/*.ts', '!src/gql/**/*'],
generates: {
'src/gql/': {
preset: 'gql-tag-operations-preset',
plugins: [],
presetConfig: {
fragmentMasking: true
}
}
}
}
export default config
After enabling fragment masking and re-generating your codegen artifacts you now have access to a useFragment
function, which is utilized for unmasking dependencies.
import { gql, FragmentType, useFragment } from '../gql'
const TweetFragment = gql(/* GraphQL */ `
fragment TweetFragment on Tweet {
id
body
}
`)
const Tweet = (props: {
/* tweet property has the correct type 🎉 */
tweet: FragmentType<typeof TweetFragment>
}) => {
// we unmask our fragment to the actual type
const tweet = useFragment(TweetFragment, props.tweet)
return <li data-id={tweet.id}>{tweet.body}</li>
}
const TweetsQuery = gql(/* GraphQL */ `
query TweetsQuery {
Tweets {
id
...TweetFragment
}
}
`)
const Tweets = () => {
const [result] = useQuery({ query: TweetsQuery })
const { data, fetching, error } = result
if (fetching) return <p>Loading…</p>
if (error) return <p>Oh no… {error.message}</p>
return (
<ul>
{data.Tweets.map(tweet => {
// TypeScript error, as the fragment properties are masked.
tweet.body
return <Tweet key={tweet.id} tweet={tweet} />
})}
</ul>
)
}
The default useFragment
implementation does only data masking on a TypeScript type level. If you log the object, you
can all properties, including the masked ones, are available on the object. For true runtime data masking, the GraphQL
client must implement it. Currently, there is no client that does this (aside from relay which should best be used
with relay-compiler).
Fragment Masking with custom unmask name
By default, the data unmask function name is useFragment
, which follows the React hook naming convention. For React users we recommend keeping that name. If you need to customize the name of the function you can provide the fragmentMasking.unmaskFunctionName
option.
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'src/path/to/your/schema.graphql',
documents: ['src/**/*.ts', '!src/gql/**/*'],
generates: {
'src/gql/': {
preset: 'gql-tag-operations-preset',
plugins: [],
presetConfig: {
fragmentMasking: {
unmaskFunctionName: 'getFragment'
}
}
}
}
}
export default config
Module augmentation with fragment masking
Similar to the gql
module augmentation configuration it is also possible to generate type definitions for the unmask function. The fragmentMasking.unmaskFunctionName
will be used when doing so.
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'src/path/to/your/schema.graphql',
documents: ['src/**/*.ts', '!src/gql/**/*'],
generates: {
'src/gql/': {
preset: 'gql-tag-operations-preset',
plugins: [],
presetConfig: {
fragmentMasking: {
augmentedModuleName: '@something/fragment'
}
}
}
}
}
export default config
Config API Reference
augmentedModuleName
type: string
Instead of generating a gql
function, this preset can also generate a d.ts
that will enhance the gql
function of your framework.
E.g. graphql-tag
or @urql/core
.
Usage Examples
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
// ...
generates: {
'path/to/file.ts': {
preset: 'gql-tag-operations',
plugins: [],
presetConfig: {
augmentedModuleName: '@urql/core'
},
},
},
};
export default config;
fragmentMasking
type: FragmentMaskingConfig | boolean
Fragment masking hides data from components and only allows accessing the data by using a unmasking function.
Usage Examples
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
// ...
generates: {
'path/to/file.ts': {
preset: 'gql-tag-operations',
plugins: [],
presetConfig: {
augmentedModuleName: '@urql/core',
fragmentMasking: {
augmentedModuleName: '@urql/fragment',
},
},
},
},
};
export default config;
gqlTagName
type: string
default: `"gql"
E.g. graphql
or gql
.`
Specify the name of the "graphql tag" function to use
Usage Examples
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
// ...
generates: {
'path/to/file.ts': {
preset: 'gql-tag-operations',
plugins: [],
presetConfig: {
gqlTagName: 'graphql'
},
},
},
};
export default config;