A webpack loader/babel-plugin/babel-plugin-macros/CLI/generated file manager of GraphQL code generator.
npm install graphql-letA webpack loader/babel-plugin/babel-plugin-macros/CLI/generated file manager of
GraphQL code generator.
Try
Create React App example
and
Next.js example
integrating graphql-let. A blog post
- Why this exists
- Entrypoints and features
- Getting started with webpack loader
- Getting started with babel-plugin-macros
- Getting started with Babel Plugin
- Difference between .graphql-let.yml and codegen.yml
- Jest Transformer
- Experimental feature: Resolver Types
- FAQ
- Contribution
- License
One of the strengths of GraphQL is
enforcing data types on runtime.
Further, TypeScript and
GraphQL code generator help it even safer
by typing your codebase statically. Both make a truly type-protected development
environment with rich IDE assists.
graphql-let enhances that development pattern by minimizing configuration setup,
introducing intuitive syntax, and comfortable development experience through HMR
(hot module replacement).
``typescript jsx
import { useNewsQuery } from './news.graphql' // webpack
// or
import { gql, load } from "graphql-let/macro" // babel-plugin-macros
const { useNewsQuery } = gql("query News { braa }")
const News: React.FC = () => {
// Typed already️⚡️
const { data: { news } } = useNewsQuery()
return
Entrypoints and features
Summary of characteristics of each entrypoint.
- CLI for efficient code generation before your type checking
- webpack loader to get HMR even on modifying GraphQL documents
- babel-plugin-macros for the minimum configuration
- Babel plugin if you don't want babel-plugin-macros
All of them mostly do the same behind the scene.
1. Loads your configuration from
.graphql-let.yml
2. Finds GraphQL documents (queries, mutations, subscriptions) from .graphql*
and .ts* specified in your config.documents
3. Processes and passes arguments to GraphQL code generator **to generate
.ts s*. These are used for runtime. s of the codegen results.Note there are a few differences between the entrypoints.
Syntax table for the entrypoints
Entry pointsYou need .graphql-let.yml and:
Getting codegen result from
Use values of codegen result
Use types of codegen result
Pros/Cons
webpack loader
Configure "graphql-let/loader"
to files "/.*\.(tsx?|graphql)$/" in webpack.config.(js|ts)
File
✅ Import both value and types from a GraphQL document as a module. import { useQuery, Query } from "./a.graphql"
HMR works as expected.
Webpack config is required even though your project only uses Babel
String literal
✅ by import { gql } from "graphql-let"
const { useQuery } = gql("query A { braa }")
⚠️ You can, but you have to find the internal d.ts. import { gql } from "graphql-let"
import {Query} from 'graphql-let/__generated__/index-A'
const { useQuery } = gql("query { braa }")
babel-plugin-macros
If you've already setupbabel-plugin-macros,no config needed any more
File
✅ by import { load } from "graphql-let/macro"
const { useQuery } = load("./a.graphql")
⚠️ You can, but you have to find the internally generated d.ts. import { load } from "graphql-let/macro"
import {Query} from 'graphql-let/__generated__/index-A'
const { useQuery } = load("./a.graphql")
Easiest to integrate if your project already has babel-plugin-macros. create-react-app is the great fit.Cannot load types from function call.
Modifying *.graphql doesn't emit HMR.
String literal
✅ by import { gql } from "graphql-let/macro"
const { useQuery } = gql("query A { braa }")
⚠️ You can, but you have to find the internally generated d.ts. import { gql } from "graphql-let/macro"
import {Query} from 'graphql-let/__generated__/index-A'
const { useQuery } = gql("query { braa }")
babel-plugin
Put "graphql-let/babel"to you .babelrc as a plugin
File
✅ by import { load } from "graphql-let"
const { useQuery } = load("./a.graphql")
⚠️ You can, but you have to find the internally generated d.ts. import { load } from "graphql-let"
import {Query} from 'graphql-let/__generated__/index-A'
const { useQuery } = load("./a.graphql")
Mostly equivalent to babel-plugin-macros, but you always need your .babelrc configuration. Possibly, "import "./a.graphql"" could be implemented, but not supported yet.Cannot load types from function call.
Modifying *.graphql doesn't emit HMR.Possibly I can make "--watch" option butlots to do for dependency management to detect file change.
String literal
✅ by import { gql } from "graphql-let"
const { useQuery } = gql("query A { braa }")
⚠️ You can, but you have to find the internally generated d.ts. import { gql } from "graphql-let"
import {Query} from 'graphql-let/__generated__/index-A'
const { useQuery } = gql("query { braa }")
Efficiency
There are things to make graphql-let light and stable.
- Sharing the processes. Generating files is expensive, so it runs less time
to run GraphQL code generator and TypeScript API.
- Caching. Embedding hashes, as your source states, reduces the number of
unnecessary processing.
- Sharing the promises. The webpack compilation in typical SSR applications as
Next.js runs targets of "node""web"
and simultaneously. If sources are the same, the compilation should
be once.
This is an example of TypeScript + React + Apollo Client on webpack. You may
want
TypeScript Vue Apollo
or
TypeScript Urql.
Please replace the corresponding lines depending on your needs.
Note graphql-let is in devDependencies.
`bashPrerequisites
yarn add -D typescript graphql
of your .graphql-let.yml.$3
Run this command to generate a configuration template.
`bash
yarn graphql-let init
This will generate .graphql-let.yml
`Next, add
graphql-codegen plugins
in it. Please note that you have to generate a TypeScript source by the
plugins.
Edit it like this:
`diff
schema: lib/type-defs.graphqls
documents:
- '*/.graphql'
- '*/.tsx'
plugins:
+ - typescript-operations
+ - typescript-react-apollo
`$3
cacheDir will have .ts(x)s that your sources will import. It's
node_modules/.cache/graphql-let by default, but you may exclude node_modules
for webpack compilation. In that case, we recommend setting up like this.`diff
schema: lib/type-defs.graphqls
documents:
- '*/.graphql'
- '*/.tsx'
plugins:
- typescript-operations
- typescript-react-apollo
+ cacheDir: .cache
`Please note that files in
cacheDir are only intermediate cache, possibly having wrong import paths. Your tsconfig.json probably complains, so give it a line for exclusion.`diff
// tsconfig.json
{
+ "excludes": [".cache"]
}
`
Also, remember you have to .gitignore the .cache directory in the next section.$3
graphql-let will generate
.d.ts files in the same folder of .graphql. Add
these lines in your .gitignore.`diff
+ *.graphql.d.ts
+ *.graphqls.d.ts
+ /.cache
`$3
The webpack loader also needs to be configured. Note that the content that
graphql-let/loader generates is JSX-TypeScript. You have to compile it to
JavaScript with an additional loader such as babel-loader.`diff
const config: Configuration = {
module: {
rules: [
+ {
+ test: /\.(tsx|graphql)$/,
+ use: [
+ { loader: 'babel-loader', options: { presets: ['@babel/preset-typescript', '@babel/preset-react'] } },
+ { loader: 'graphql-let/loader' },
+ ]
+ }
]
}
}
`$3
Run this to generate
.d.ts.`bash
yarn graphql-letThis will generate files such as:
- src/query.graphql.d.ts
- src/schema.graphqls.d.ts
`By
--config option, you can specify the custom path to the .graphql-let.yml.
The directory .graphql-let.yml is located at is the basepath of the relative
paths in .grpahql-let.yml. Also, the basepath should be identical to webpack's
config.context so the loader can find the config file.`bash
pwd # "/app"
yarn graphql-let --config custom/path/.graphql-let.ymlThis will point paths such as:
/app/custom/path/src/query.graphql.d.ts
/app/custom/path/src/schema.graphqls.d.ts
`You may want to run it every time before calling
tsc. Please check your
package.json and modify like this.`diff
"scripts": {
- "build": "tsc"
+ "build": "graphql-let && tsc"
},
`$3
Enjoy HMR (Hot Module Replacement) of webpack with the generated react-apollo
hooks and IDE code assists.
`typescript jsx
import { gql } from 'graphql-let'
import { useNewsQuery } from './news.graphql'const { useViewerQuery } = gql(
query Viewer { blaa })const News: React.FC = () => {
// Already typed⚡️
const { data: { news } } = useNewsQuery()
const { data: { viewer } } = useViewerQuery()
return
{ news.map(...) }
}
`Getting started with babel-plugin-macros
babel-plugin-macros
requires the least configuration to setup.
Please finish 1. Install dependencies, and
2. Configure .graphql-let.yml as you still need
.graphql-let.yml.
$3
[Put a line
"plugins": ["macros"] to your .babelrc](https://github.com/kentcdodds/babel-plugin-macros/blob/main/other/docs/user.md#via-babelrc-recommended).
If you use Create React App, it contains
babel-plugin-macros out of the box.If you want a custom path to .graphql-let.yml, you can use
configFilePath
babel option. should point to your
.graphql-let.yml.$3
Thanks to babel-plugin-macros's beautiful architecture, you're ready to use
GraphQL codegen values.
`typescript jsx
import { gql, load } from "graphql-let/macro"// Typed⚡️
const { useNewsQuery } = gql("query News { braa }")
const { useViewerQuery } = load("./viewer.graphql")
`Note that your schema types are generated in
graphql-let/__generated__/__types__, instead of per-document outputs.`typescript jsx
import { News } from 'graphql-let/__generated__/__types__'
`Getting started with Babel Plugin
Mostly the same as babel-plugin-macros, only you need to
import "graphql-let".Please finish 1. Install dependencies and
2. Configure .graphql-let.yml as you still need
.graphql-let.yml.
$3
`diff
{
+ "plugins": ["graphql-let/babel"]
}
`$3
`typescript jsx
import { gql, load } from "graphql-let"const { useNewsQuery } = gql("query News { braa }")
const { useViewerQuery } = load("./viewer.graphql")
`Difference between .graphql-let.yml and codegen.yml
graphql-let half passes your config options to GraphQL code generator API and
half controls them. Here explains how different these and why. You can see this
section as a migration guide, too.
`diff
schema: https://api.github.com/graphql
documents: "*/.graphql"
- generates:
- ./__generated__/operations.ts:
- config:
- key: value
- plugins:
- - typescript
- - typescript-operations
- preset: xxx
+ plugins:
+ - typescript-operations
+ config:
+ key: value
`$3
You have to have
@graphql-codegen/typescript as a dev dependency. graphql-let
generates types by default, where it uses the plugin. The plugins in
.graphql-let.yml is for per-document, which imports the shared types
automatically. If you specify typescript as a plugin, it's
still okay, but you can imagine it's kind of redundant.$3
codegen.yml has an option
generates, but it's strictly controlled under
graphql-let. Rather, think graphql-let as a tool to let you forget intermediate
outputs and import/call GraphQL directly.Therefore, we don't support output-file level configuration such as
Output-file level schema,
Output-file level documents,
and
Output Level config
right now. But this could be changed logically, so please
vote by issuing if you'd
like.
$3
Presets
decide how to split/import each other, which graphql-let manages basically.
graphql-let generates per-document
.d.ts and binds up schema types into a
shared file, that's why
@graphql-codegen/import-types-preset
is our peer dependency.I think you don't need to configure Presets, because graphql-let takes care of
what Presets does on your behalf. If you notice the use-case you need more
flexibility, please issue it.
$3
Document-level options such as
noRequir or
Custom Document Loader
are not supported.$3
In addition to
codegen.yml options, graphql-let accepts these.`yaml
"plugins", required. The plugins for GraphQL documents to run GraphQL code
generator with. You should omit
typescript plugin which graphql-let generates internally.
See here for more information. https://graphql-code-generator.com/docs/plugins/index
Example:
plugins:
- typescript-operations
- typescript-react-apollo
- add: "/ eslint-disable /""respectGitIgnore", optional.
true by default.
If true, graphql-let will ignore files in .gitignore.
Useful to prevent parsing files in such as
node_modules.
respectGitIgnore: true"cacheDir", optional.
node_modules/.cache/graphql-let by default.
graphql-let takes care of intermediate
.ts(x)s that GraphQL code generator
generates, but we still need to write them on the disk for caching and
TypeScript API purposes. This is the directory we store them to.
Examples:
cacheDir: node_modules/.cache/graphql-let
cacheDir: .cache"TSConfigFile", optional.
tsconfig.json by default.
You can specify a custom config for generating
.d.tss.
Examples:
TSConfigFile: tsconfig.json
TSConfigFile: tsconfig.compile.json"typeInjectEntrypoint", optional.
node_modules/@types/graphql-let/index.d.ts by default. Needs to end with ".d.ts".
Used as an entrypoint and directory of generated type declarations
for
gql() and load() calls.
typeInjectEntrypoint: node_modules/@types/graphql-let/index.d.ts"silent", optional.
false by default.
Pass
true if you want to suppress all standard output from graphql-let.
silent: false
`Simple example:
`yaml
schema: "schema/*/.graphqls"
documents:
- "*/.graphql"
- "!shouldBeIgnored1"
plugins:
- typescript-operations
- typescript-react-apollo
`Example with a bit more complicated options:
`yaml
schema:
- https://api.github.com/graphql:
headers:
Authorization: YOUR-TOKEN-HERE
documents:
- "*/.graphql"
- "!shouldBeIgnored1"
plugins:
- typescript-operations
- typescript-react-apollo
respectGitIgnore: true
config:
reactApolloVersion: 3
apolloReactComponentsImportFrom: "@apollo/client/react/components"
useIndexSignature: true
cacheDir: .cache
TSConfigFile: tsconfig.compile.json
typeInjectEntrypoint: typings/graphql-let.d.ts
`$3
- Sadly, type injection can't be done with TaggedTemplateExpression such
as
gqlquery {} . This is the limitation of TypeScript.
Please answer me if you have any ideas.
- Fragments are still not available. Please watch
the issue.Jest Transformer
graphql-let/jestTransformer is available. Configure your jest.config.js as:`diff
module.exports = {
transform: {
+ "\\.graphql$": "graphql-let/jestTransformer",
},
};
`$3
babel-jest is the default subsequent transformer of
graphql-let/jestTransformer. Install these:`bash
yarn add -D graphql-let babel-jest
`And make sure your babel config can compile generated
.ts(x)s.$3
The option
subsequentTransformer is available. If you use ts-jest, your
jest.config.js will look like this:`diff
const { defaults: tsjPreset } = require("ts-jest/presets"); module.exports = {
preset: "ts-jest",
transform: {
...tsjPreset.transform,
+ "\\.graphql$": [
+ "graphql-let/jestTransformer",
+ { subsequentTransformer: "ts-jest" },
+ ],
},
};
`$3
If you use
graphql-let/schema/loader, you may want a corresponding
transformer, but remember graphql-let does not transform the content of GraphQL
schema. Just use what you need; it's most likely to be jest-transform-graphql.`diff
module.exports = {
transform: {
"\\.graphql$": "graphql-let/jestTransformer",
+ "\\.graphqls$": "jest-transform-graphql",
},
};
`Experimental feature: Resolver Types
If you meet the following conditions, graphql-let generates Resolver Types.
- You have file paths including glob patterns in
schema
- You have
@graphql-codegen/typescript-resolvers
installedRun:
`bash
yarn add -D @graphql-codegen/typescript-resolvers
yarn graphql-let
`Then you will get resolver types in
graphql-let/__generated__/__types__.`typescript
import { Resolvers } from "graphql-let/__generated__/__types__";const resolvers: Resolvers = {
Query: {
// All typed⚡️
viewer(parent, args, context, info) {
return { ... }
},
}
};
export default resolvers;
`graphql-let/schema/loader is also available to update resolver types. It doesn't transpile anything;
just detects file modification and passes the content to the next loader.`diff
// webpack.config.ts
const config: Configuration = {
module: {
rules: [
+ {
+ test: /\.graphqls$/,
+ use: [
+ { loader: 'graphql-let/schema/loader' },
+ ]
+ }
]
}
}
`FAQ
#### So, it's just a graphql-codegen wrapper generating
d.ts...?_Yes._
#### Supported combination of tools? / Tools x + y don't work!
The above documentation should work basically, but some of the combinations may
require more effort. Please vote by creating issues.
Sponsoring me is another way to get
my attention🍩🍦👀
These are the states/tools for the syntaxes.
| states/tools for syntax | import GraphQL document as
import './a.graphql'; | Inline GraphQL document as
import {gql} from 'graphql-let';
gql(query {} ); |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| generating .d.tss by command graphql-let | ✅ | ✅ |
| importing GraphQL content from another as
# import A from './a.graphql' | ✅ | ✅ |
| webpack loader graphql-let/loader | ✅ | ✅ |
| Babel Plugin graphql-let/babel | ✅ | ✅ |
| Jest Transformer graphql-let/jestTransfomer | ✅ | Vote by issuing |
| Experimental: Resolver Types for
GraphQL schema | ✅ by
import {Resolvers}
from 'graphql-let/__generated__/__types__' | (I think we don't need this) |#### Is this a tool only for React?
No. There are
more plugins that also generates
.ts(x)s from GraphQL documents.#### Can I use Tagged Template as
gqlquery News { baa }; ?Sadly, you need
gql() instead of gql because of
the limitation of TypeScript.#### What's the extensions
.graphql and .graphqls? Can I use .gql or something else?You can use what you want. I wanted to recommend distinguishing GraphQL schema
and GraphQL documents in the extensions, which will lead to a more
understandable configuration for webpack loaders with fewer pitfalls. Another
reason for
.graphqls is that it's one of
the supported extensions in the internal library.#### How to integrate Apollo refetchQueries?
DocumentNode named ${QueryName}Document that you can make use of.#### How to import
.graphql from another document, especially GraphQL Fragment?graphql-tools/import,
the syntax # import X from './fragment.graphql' is supported.Define your fragment named as
partial.graphql`graphql
fragment Partial on User {
id
name
}
`and import it.
`graphql
import Partial from './partial.graphql'
query Viewer {
viewer {
...Partial
}
}
`####
.tsxes generated in cacheDir (.cache) throw TypeScript errors of wrong import pathsIt's not a bug. Please exclude
cacheDir from your TypeScript compilation. The files in cacheDir are only intermediates, which will speed your next execution.`
Your GraphQL documents -> (call GraphQL code generator API 1) -> .tsx 2 -> (call TypeScript to distribute declarations *3) -> .d.ts
`You're seeing the
2. It's used to skip 1 and *3, and recodnized as generated implementations, which graphql-let/loader returns, for example.Contribution
- Create an issue
if you have ideas, find a bug, or anything.
- Creating a PR is always welcome!
- Running
npm run prepublishOnly` locally will get your local developmentApache License Version 2.0