Skip to content

Commit

Permalink
Merge pull request #90 from piglovesyou/canary
Browse files Browse the repository at this point in the history
Writes `d.ts` in the same directory of the corresponding `.graphql`s, etc.
  • Loading branch information
piglovesyou authored Apr 28, 2020
2 parents 96f2fc9 + ba74b07 commit daf5a49
Show file tree
Hide file tree
Showing 43 changed files with 355 additions and 355 deletions.
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,5 @@ dist
# To develop with closer deps version that users will use
yarn.lock

*.graphql.d.ts

.next
test/**/*.graphql.d.ts
test/**/*.graphqls.d.ts
132 changes: 82 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
# graphql-let ![](https://github.com/piglovesyou/graphql-let/workflows/Node%20CI/badge.svg) [![npm version](https://badge.fury.io/js/graphql-let.svg)](https://badge.fury.io/js/graphql-let)
# graphql-let [![](https://github.com/piglovesyou/graphql-let/workflows/Node%20CI/badge.svg)](https://github.com/piglovesyou/graphql-let/actions) [![npm version](https://badge.fury.io/js/graphql-let.svg)](https://badge.fury.io/js/graphql-let)

A webpack loader to import type-protected codegen results directly from GraphQL documents.
A webpack loader to import type-protected codegen results directly from GraphQL
documents.

Try [the Next.js example](https://github.com/zeit/next.js/blob/canary/examples/with-typescript-graphql/README.md#readme) that integrates graphql-let.
Try
[the Next.js example](https://github.com/zeit/next.js/blob/canary/examples/with-typescript-graphql/README.md#readme)
that integrates graphql-let.

## Why it exists

One of the strengths of GraphQL is [enforcing data types on runtime](https://graphql.github.io/graphql-spec/June2018/#sec-Value-Completion). Further, TypeScript and [GraphQL Code Generator](https://graphql-code-generator.com/) (graphql-codegen) make it safer by typing data statically, so you can write truly type-protected code with rich IDE assists.
One of the strengths of GraphQL is
[enforcing data types on runtime](https://graphql.github.io/graphql-spec/June2018/#sec-Value-Completion).
Further, TypeScript and
[GraphQL Code Generator](https://graphql-code-generator.com/) (graphql-codegen)
make it safer by typing data statically, so you can write truly type-protected
code with rich IDE assists.

To enhance the development pattern, it's necessary to focus on a more specific use-case than what graphql-codegen allows; binding TypeScript (and assuming the use of Apollo-Client and React). In the way, graphql-let behaves as a subset of graphql-codegen.
To enhance the development pattern, it's necessary to focus on a more specific
use-case than what graphql-codegen allows; binding TypeScript (and assuming the
use of Apollo-Client and React). In the way, graphql-let behaves as a subset of
graphql-codegen.

graphql-let lets you import graphql-codegen results directly per GraphQL documents with TypeScript type definitions by webpack Loader power.
graphql-let lets you import graphql-codegen results directly per GraphQL
documents with TypeScript type definitions by webpack Loader power.

```typescript jsx
import { useNewsQuery } from './news.graphql'
Expand All @@ -26,21 +38,26 @@ const News: React.FC = () => {

Two things:

* It runs graphql-codegen inside according to the `.graphql-let.yml` and pass the generated TypeScript source to the next loader.
* It generates a file `.d.ts`.
- It runs graphql-codegen inside according to the `.graphql-let.yml` and pass
the generated TypeScript source to the next loader.
- It generates a file `.d.ts`.

<p align="center"><img src="./resource/graphql-let-loader.png" /></p>

You may also want only `.d.ts` before a webpack build to check types. Run `graphql-let` command to get `.d.ts` without running webpack.
You may also want only `.d.ts` before a webpack build to check types. Run
`graphql-let` command to get `.d.ts` without running webpack.

<p align="center"><img src="./resource/graphql-let.png" /></p>

## Get started

This is an example of **TypeScript + React + Apollo Client**. Help yourself to replace the corresponding lines depending on your needs.
This is an example of **TypeScript + React + Apollo Client**. Please replace the
corresponding lines depending on your needs.

### 1. Install dependencies

Note graphql-let is `devDependencies`.

```
npm install -D graphql-let @graphql-codegen/cli @graphql-codegen/plugin-helpers @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
npm install @apollo/react-common @apollo/react-components @apollo/react-hooks
Expand All @@ -57,12 +74,14 @@ npx graphql-let init
# This will generate .graphql-let.yml
```

Next add [graphql-codegen plugins](https://graphql-code-generator.com/docs/plugins/#available-plugins) in it. **Please note that you have to generate TypeScript source** by the plugins.
Next add
[graphql-codegen plugins](https://graphql-code-generator.com/docs/plugins/#available-plugins)
in it. **Please note that you have to generate TypeScript source** by the
plugins.

Edit it like this:

```diff
generateDir: __generated__
schema: **/*.graphqls
documents: **/*.graphql
plugins:
Expand All @@ -73,40 +92,29 @@ Edit it like this:

Available options:

| property | required | type | meaning | examples |
| --- | :---: | --- | --- | --- |
| `generateDir` | ✔︎ | `string` | The directory that graphql-let puts generated files. You may want to ignore the directory by `.gitignore`. | `__generated__` |
| `schema` | ✔︎ | `string` | The GraphQL schema info that graphql-let requests introspection to. | <ul><li>`http://localhost:3000/graphql`</li><li>`schema.json`</li><li>`schema.graphqls`</li><li>`graphql/**/*.graphqls`</li></ul>[All available formats](https://graphql-code-generator.com/docs/getting-started/schema-field#available-formats) |
| `documents` | ✔︎ | `string \| string[]` | The GraphQL documents info of quereis and mutations etc. All the documents have to be separate files. | `./queries-and-mutations/**/*.graphql` |
| `plugins` | ✔︎ | `string[]` | The plugin names of graphql-codegen. | <ul><li>`typescript-operations`</li><li>`typescript-react-apollo`</li></ul>[All available plugins](https://graphql-code-generator.com/docs/plugins/) |
| `respectGitIgnore` | ✔︎ | `boolean` | Whether to use `.gitignore` to ignore like `node_modules`. It's passed to [globby](https://www.npmjs.com/package/globby#gitignore) internally. | `true` |
| `config` | | `Record<string, boolean \| string>` | The configuration for the plugins. [more info](https://graphql-code-generator.com/docs/getting-started/config-field) | These are configured by default. <ul><li>`withHOC: false`</li><li>`withHooks: true`</li></ul> |

#### tsconfig.json

graphql-let will generate `.d.ts` files in `__generated__/types`. Mark the directory as one of `typeRoots` in your tsconfig.json.

```diff
{
"compilerOptions": {
+ "typeRoots": [
+ "node_modules/@types",
+ "__generated__/types"
+ ]
},
```
| property | required | type | meaning | examples |
| ------------------ | :------: | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `schema` | ✔︎ | `string` | The GraphQL schema info that graphql-let requests introspection to. | <ul><li>`http://localhost:3000/graphql`</li><li>`schema.json`</li><li>`schema.graphqls`</li><li>`graphql/**/*.graphqls`</li></ul>[All available formats](https://graphql-code-generator.com/docs/getting-started/schema-field#available-formats) |
| `documents` | ✔︎ | `string \| string[]` | The GraphQL documents info of quereis and mutations etc. All the documents have to be separate files. | `./queries-and-mutations/**/*.graphql` |
| `plugins` | ✔︎ | `string[]` | The plugin names of graphql-codegen. | <ul><li>`typescript-operations`</li><li>`typescript-react-apollo`</li></ul>[All available plugins](https://graphql-code-generator.com/docs/plugins/) |
| `respectGitIgnore` | ✔︎ | `boolean` | Whether to use `.gitignore` to ignore like `node_modules`. It's passed to [globby](https://www.npmjs.com/package/globby#gitignore) internally. | `true` |
| `config` | | `Record<string, boolean \| string>` | The configuration for the plugins. [more info](https://graphql-code-generator.com/docs/getting-started/config-field) | These are configured by default. <ul><li>`withHOC: false`</li><li>`withHooks: true`</li></ul> |

#### .gitignore

You may want to exclude auto-generated files by graphql-let. Add this line in your .gitignore.
graphql-let will generate `.d.ts` files in the same folder of `.graphql`
and `.graphqls`. Add these line in your .gitignore.

```diff
+__generated__
+*.graphql.d.ts
+*.graphqls.d.ts
```

#### webpack.config.ts

The webpack loader also needs to be configured. Note that the content `graphql-let/loader` generates is JSX-TypeScript. You have to compile it to JavaScript with an additional loader such as `babel-loader`.
The webpack loader also needs to be configured. Note that the content
`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 = {
Expand All @@ -124,18 +132,32 @@ The webpack loader also needs to be configured. Note that the content `graphql-l
}
```

### 3. Prepare types
### 3. Generate type declarations

Run this command to generate `.d.ts` for `.graphql`. You may want to run it every time before running `tsc`. Please check your npm scripts in `package.json`.
Run this to generate `.d.ts`.

```
npx graphql-let
# This will generate __generated__/types/news.graphql-${hash}.d.ts
# This will generate files such as:
# - src/query.graphql.d.ts
# - src/schema.graphqls.d.ts
```

You may want to run it everytime calling `tsc`. Please check your `package.json`
and modify like this.

```diff
"scripts": {
- "build": "tsc"
+ "build": "graphql-let && tsc"
},
```

### 4. Code more

Enjoy the webpack Hot Module Replacement with the generated react-apollo hooks and IDE code assists.
Enjoy HMR (Hot Module Replacement) of webpack with the generated
react-apollo hooks and IDE code assists.

```typescript jsx
import { useNewsQuery } from './news.graphql'
Expand All @@ -151,10 +173,13 @@ const News: React.FC = () => {

If you:

* have local GraphQL schema files (`.graphqls`)
* have installed [`@graphql-codegen/typescript-resolvers`](https://graphql-code-generator.com/docs/plugins/typescript-resolvers) in dependencies
- have a single local GraphQL schema file (`.graphqls`)
- have installed
[`@graphql-codegen/typescript-resolvers`](https://graphql-code-generator.com/docs/plugins/typescript-resolvers)
in dependencies

, graphql-let command will generate `__concatedschema__-*.d.ts` to help you write GraphQL resolvers. Run:
, graphql-let will generate `.graphqls.d.ts` to help you type GraphQL resolvers.
Run:

```bash
yarn add -D @graphql-codegen/typescript-resolvers
Expand All @@ -179,7 +204,8 @@ const resolvers: Resolvers = {
export default resolvers;
```

`graphql-let/schema/loader` is also available. It just pass GraphQL Content to the next loader but it generates resolver types. Set it up like this:
`graphql-let/schema/loader` is also available. It just pass GraphQL Content to
the next loader but it generates resolver types in HMR. Set it up like this:

```diff
const config: Configuration = {
Expand All @@ -205,23 +231,29 @@ _Yes._

#### Is this a tool only for React?

No. There are [more plugins that also generates `.ts` from GraphQL documents](https://graphql-code-generator.com/docs/plugins/).
No. There are
[more plugins that also generates `.ts` from GraphQL documents](https://graphql-code-generator.com/docs/plugins/).

#### Can I write GraphQL documents in my `.tsx` as ``const query = gql`query News{ ... }`;``?
#### Can I write GraphQL documents in my `.tsx` as `` const query = gql`query News{ ... }`; ``?

Afraid not. You need to have separate files to execute the webpack loader. Besides, typing the value of ``gql`...` `` would be impossible.
Afraid not. You need to have separate files to execute the webpack loader.
Besides, typing the value of `` gql`...` `` would be impossible.

#### What's the extension `.graphqls`? Should I use it for schema and `.graphql` for documents?

Not exactly, but I'd recommend them. I think using different extensions for schema/documents leads 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](https://github.com/ardatan/graphql-toolkit/blob/d29e518a655c02e3e14377c8c7d3de61f08e6200/packages/loaders/graphql-file/src/index.ts#L9).
Not exactly, but I'd recommend them. I think using different extensions for
schema/documents leads 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](https://github.com/ardatan/graphql-toolkit/blob/d29e518a655c02e3e14377c8c7d3de61f08e6200/packages/loaders/graphql-file/src/index.ts#L9).

#### How to integrate Apollo refetchQueries?

[Query document exports `DocumentNode` named `${QueryName}Document` that you can make use of it.](https://github.com/piglovesyou/graphql-let/issues/66#issuecomment-596276493)

#### How to import another file for GraphQL Fragment?

You can't yet. [Please watch the progress.](https://github.com/piglovesyou/graphql-let/issues/65)
You can't yet.
[Please watch the progress.](https://github.com/piglovesyou/graphql-let/issues/65)

## License

Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graphql-let",
"version": "0.9.6",
"version": "0.10.0-canary.1",
"repository": {
"type": "git",
"url": "[email protected]:piglovesyou/graphql-let.git"
Expand Down Expand Up @@ -102,9 +102,15 @@
"git add --force"
]
},
"*.{md,graphql}": [
"yarn _fix-nonjs",
"git add --force"
],
"scripts": {
"lint": "eslint --ext .ts,.tsx --ignore-path .gitignore .",
"fix": "yarn lint --fix",
"_fix-nonjs": "prettier --no-config --write --tab-width 4 --print-width 80 --prose-wrap always --ignore-path .gitignore",
"fix-nonjs": "yarn _fix-nonjs '**/*.{md,graphql}'",
"fix": "yarn lint --fix && prettier --write",
"compile": "tsc --project tsconfig.compile.json",
"clean": "rimraf \"**/__generated__\" dist",
"test": "jest",
Expand Down
10 changes: 2 additions & 8 deletions src/gen.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fullGenerate from './lib/full-generate';
import loadConfig from './lib/load-config';
import { createDtsRelDir } from './lib/paths';
import { PRINT_PREFIX } from './lib/print';
import { CommandOpts } from './lib/types';
import logUpdate from 'log-update';
Expand All @@ -14,14 +13,9 @@ export default async function gen(commandOpts: CommandOpts): Promise<void> {
const numberAffected = await fullGenerate(cwd, config, configHash);

if (numberAffected) {
logUpdate(
PRINT_PREFIX +
`${numberAffected} .d.ts were generated in ${createDtsRelDir(
config.generateDir,
)}.`,
);
logUpdate(PRINT_PREFIX + `${numberAffected} .d.ts were generated.`);
} else {
logUpdate(PRINT_PREFIX + `Done nothing, caches are still fresh.`);
logUpdate(PRINT_PREFIX + `Done nothing, caches are fresh.`);
}
logUpdate.done();
}
1 change: 0 additions & 1 deletion src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { printInfo } from './lib/print';
import { CommandOpts, ConfigTypes } from './lib/types';

const DEFAULT_CONFIG: ConfigTypes = {
generateDir: '__generated__',
schema: '**/*.graphqls',
documents: '**/*.graphql',
plugins: ['typescript'],
Expand Down
5 changes: 3 additions & 2 deletions src/lib/dts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import makeDir from 'make-dir';
import path from 'path';
import { createCompilerHost, createProgram, CompilerOptions } from 'typescript';
import { writeFile } from './file';
import { withHash, writeFile } from './file';

const options: CompilerOptions = {
declaration: true,
Expand Down Expand Up @@ -58,10 +58,11 @@ export async function processGenDts(
dtsFullPath: string,
tsxFullPath: string,
gqlRelPath: string,
sourceHash: string,
) {
await makeDir(path.dirname(dtsFullPath));
const [dtsContent] = await genDts([tsxFullPath]);
if (!dtsContent) throw new Error(`Generate ${dtsFullPath} fails.`);
await writeFile(dtsFullPath, wrapAsModule(gqlRelPath, dtsContent));
await writeFile(dtsFullPath, withHash(sourceHash, dtsContent));
return dtsContent;
}
31 changes: 31 additions & 0 deletions src/lib/file.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { promises as fsPromises } from 'fs';
import { createReadStream, existsSync } from 'fs';
import glob from 'globby';
import _rimraf from 'rimraf';
import { promisify } from 'util';
Expand All @@ -16,3 +17,33 @@ export async function removeByPatterns(cwd: string, ...patterns: string[]) {
});
await Promise.all(oldFiles.map((f) => rimraf(f)));
}

const leadingStringOfGeneratedContent = '/* ';
const hexHashLength = 40;
export function withHash(sourceHash: string, content: string): string {
return `${leadingStringOfGeneratedContent}${sourceHash}
* This file is automatically generated by graphql-let. */
${content}`;
}

export function readHash(filePath: string): Promise<string | null> {
if (!existsSync(filePath)) {
return Promise.resolve(null);
}

return new Promise((resolve, reject) => {
const stream = createReadStream(filePath, {
encoding: 'utf-8',
highWaterMark: leadingStringOfGeneratedContent.length + hexHashLength,
});
stream.on('error', (error) => reject(error));
stream.on('data', (chunk: string) => {
const hash = chunk.slice(leadingStringOfGeneratedContent.length);
if (hash.length !== hexHashLength) return resolve(null);

resolve(hash);
});
stream.read();
});
}
Loading

0 comments on commit daf5a49

Please sign in to comment.