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

Urql support #1814

Merged
merged 6 commits into from
May 7, 2019
Merged
Show file tree
Hide file tree
Changes from 4 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
5 changes: 5 additions & 0 deletions packages/plugins/typescript/urql/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
npm-debug.log
dist
temp
yarn-error.log
5 changes: 5 additions & 0 deletions packages/plugins/typescript/urql/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
src
node_modules
tests
!dist
example
41 changes: 41 additions & 0 deletions packages/plugins/typescript/urql/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@graphql-codegen/typescript-urql",
"version": "1.1.3",
"description": "GraphQL Code Generator plugin for generating a ready-to-use React Components/HOC/Hooks based on GraphQL operations with urql",
"repository": "[email protected]:dotansimha/graphql-code-generator.git",
"license": "MIT",
"scripts": {
"build": "tsc -m esnext --outDir dist/esnext && tsc -m commonjs --outDir dist/commonjs",
"test": "jest --config ../../../../jest.config.js"
},
"peerDependencies": {
"graphql-tag": "^2.0.0"
},
"dependencies": {
"@graphql-codegen/plugin-helpers": "1.1.3",
"@graphql-codegen/visitor-plugin-common": "1.1.3",
"tslib": "1.9.3"
},
"devDependencies": {
"@graphql-codegen/testing": "1.1.3",
"flow-bin": "0.98.0",
"flow-parser": "0.98.0",
"graphql": "14.2.1",
"jest": "24.8.0",
"ts-jest": "24.0.2",
"typescript": "3.4.5"
},
"sideEffects": false,
"main": "dist/commonjs/index.js",
"module": "dist/esnext/index.js",
"typings": "dist/esnext/index.d.ts",
"typescript": {
"definition": "dist/esnext/index.d.ts"
},
"jest-junit": {
"outputDirectory": "../../../../test-results/typescript-react-apollo"
},
"publishConfig": {
"access": "public"
}
}
87 changes: 87 additions & 0 deletions packages/plugins/typescript/urql/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Types, PluginValidateFn, PluginFunction } from '@graphql-codegen/plugin-helpers';
import { visit, GraphQLSchema, concatAST, Kind, FragmentDefinitionNode } from 'graphql';
import { RawClientSideBasePluginConfig } from '@graphql-codegen/visitor-plugin-common';
import { UrqlVisitor } from './visitor';
import { extname } from 'path';

export interface UrqlRawPluginConfig extends RawClientSideBasePluginConfig {
/**
* @name withComponent
* @type boolean
* @description Customized the output by enabling/disabling the generated Component.
* @default true
*
* @example
* ```yml
* generates:
* path/to/file.ts:
* plugins:
* - typescript
* - typescript-operations
* - typescript-react-apollo
* config:
* withComponent: false
* ```
*/
withComponent?: boolean;
/**
* @name withHooks
* @type boolean
* @description Customized the output by enabling/disabling the generated React Hooks.
* @default false
*
* @example
* ```yml
* generates:
* path/to/file.ts:
* plugins:
* - typescript
* - typescript-operations
* - typescript-react-apollo
* config:
* withHooks: false
* ```
*/
withHooks?: boolean;

/**
* @name urqlImportFrom
* @type string
* @description You can specify module that exports components `Query`, `Mutation`, `Subscription` and HOCs
* This is useful for further abstraction of some common tasks (eg. error handling).
* Filepath relative to generated file can be also specified.
* @default urql
*/
urqlImportFrom?: string;
}

export const plugin: PluginFunction<UrqlRawPluginConfig> = (schema: GraphQLSchema, documents: Types.DocumentFile[], config: UrqlRawPluginConfig) => {
const allAst = concatAST(
documents.reduce((prev, v) => {
return [...prev, v.content];
}, [])
);
const operationsCount = allAst.definitions.filter(d => d.kind === Kind.OPERATION_DEFINITION);

if (operationsCount.length === 0) {
return '';
}

const allFragments = allAst.definitions.filter(d => d.kind === Kind.FRAGMENT_DEFINITION) as FragmentDefinitionNode[];
const visitor = new UrqlVisitor(allFragments, config) as any;
const visitorResult = visit(allAst, { leave: visitor });

return [visitor.getImports(), visitor.fragments, ...visitorResult.definitions.filter(t => typeof t === 'string')].join('\n');
};

export const validate: PluginValidateFn<any> = async (schema: GraphQLSchema, documents: Types.DocumentFile[], config: UrqlRawPluginConfig, outputFile: string) => {
if (config.withComponent === false) {
if (extname(outputFile) !== '.ts' && extname(outputFile) !== '.tsx') {
throw new Error(`Plugin "react-apollo" with "noComponents" requires extension to be ".ts" or ".tsx"!`);
}
} else {
if (extname(outputFile) !== '.tsx') {
throw new Error(`Plugin "react-apollo" requires extension to be ".tsx"!`);
}
}
};
75 changes: 75 additions & 0 deletions packages/plugins/typescript/urql/src/visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ClientSideBaseVisitor, ClientSideBasePluginConfig, getConfigValue } from '@graphql-codegen/visitor-plugin-common';
import { UrqlRawPluginConfig } from './index';
import * as autoBind from 'auto-bind';
import { FragmentDefinitionNode, OperationDefinitionNode, Kind } from 'graphql';
import { toPascalCase } from '@graphql-codegen/plugin-helpers';
import { titleCase } from 'change-case';

export interface UrqlPluginConfig extends ClientSideBasePluginConfig {
withComponent: boolean;
withHooks: boolean;
urqlImportFrom: string;
}

export class UrqlVisitor extends ClientSideBaseVisitor<UrqlRawPluginConfig, UrqlPluginConfig> {
constructor(fragments: FragmentDefinitionNode[], rawConfig: UrqlRawPluginConfig) {
super(fragments, rawConfig, {
withComponent: getConfigValue(rawConfig.withComponent, true),
withHooks: getConfigValue(rawConfig.withHooks, false),
urqlImportFrom: getConfigValue(rawConfig.urqlImportFrom, null),
} as any);
mxstbr marked this conversation as resolved.
Show resolved Hide resolved

autoBind(this);
}

public getImports(): string {
const baseImports = super.getImports();
const imports = [];

if (this.config.withComponent) {
imports.push(`import * as React from 'react';`);
}

if (this.config.withComponent || this.config.withHooks) {
imports.push(`import * as Urql from '${this.config.urqlImportFrom || 'urql'}';`);
}

imports.push(`export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>`);

return [baseImports, ...imports].join('\n');
}

private _buildComponent(node: OperationDefinitionNode, documentVariableName: string, operationType: string, operationResultType: string, operationVariablesTypes: string): string {
const componentName: string = this.convertName(node.name.value, { suffix: 'Component', useTypesPrefix: false });

const isVariablesRequired = operationType === 'Query' && node.variableDefinitions.some(variableDef => variableDef.type.kind === Kind.NON_NULL_TYPE);

return `
export const ${componentName} = (props: { variables${isVariablesRequired ? '' : '?'}: ${operationVariablesTypes}${operationType === 'Query' ? ', requestPolicy?: Urql.RequestPolicy' : ''} }) => (
<Urql.${operationType} query={${documentVariableName}} {...props} />
);
`;
}

private _buildHooks(node: OperationDefinitionNode, operationType: string, documentVariableName: string, operationResultType: string, operationVariablesTypes: string): string {
const operationName: string = this.convertName(node.name.value, { suffix: titleCase(operationType), useTypesPrefix: false });

if (operationType === 'Mutation') {
return `
export function use${operationName}() {
return Urql.use${operationType}<${operationResultType}>(${documentVariableName});
};`;
}
return `
export function use${operationName}(options: Urql.Use${operationType}Args<${operationVariablesTypes}> = {}) {
return Urql.use${operationType}<${operationResultType}>({ query: ${documentVariableName}, ...options });
};`;
}

protected buildOperation(node: OperationDefinitionNode, documentVariableName: string, operationType: string, operationResultType: string, operationVariablesTypes: string): string {
const component = this.config.withComponent ? this._buildComponent(node, documentVariableName, operationType, operationResultType, operationVariablesTypes) : null;
const hooks = this.config.withHooks ? this._buildHooks(node, operationType, documentVariableName, operationResultType, operationVariablesTypes) : null;

return [component, hooks].filter(a => a).join('\n');
}
}
Loading