Skip to content

Commit

Permalink
Support new React Apollo API (#2182)
Browse files Browse the repository at this point in the history
* Support new React Apollo API

* Support new React Apollo API
  • Loading branch information
ardatan committed Aug 6, 2019
1 parent 680e7b3 commit adf605b
Show file tree
Hide file tree
Showing 11 changed files with 490 additions and 421 deletions.
100 changes: 51 additions & 49 deletions dev-test/githunt/types.reactApollo.customSuffix.tsx

Large diffs are not rendered by default.

130 changes: 66 additions & 64 deletions dev-test/githunt/types.reactApollo.hooks.tsx

Large diffs are not rendered by default.

100 changes: 51 additions & 49 deletions dev-test/githunt/types.reactApollo.preResolveTypes.tsx

Large diffs are not rendered by default.

100 changes: 51 additions & 49 deletions dev-test/githunt/types.reactApollo.tsx

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
"@types/mkdirp": "0.5.2",
"@types/node": "10.14.14",
"@types/request": "2.48.2",
"@apollo/react-common": "3.0.0",
"@apollo/react-components": "3.0.0",
"@apollo/react-hoc": "3.0.0",
"@apollo/react-hooks": "3.0.0",
"apollo-link": "1.2.12",
"apollo-server": "2.8.1",
"graphql": "14.4.2",
Expand All @@ -51,8 +55,6 @@
"jest-junit": "7.0.0",
"lerna": "3.16.4",
"lint-staged": "9.2.1",
"react-apollo": "2.5.8",
"react-apollo-hooks": "0.5.0",
"urql": "1.3.0",
"rimraf": "2.6.3",
"ts-jest": "24.0.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql-codegen-cli/src/utils/debugging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function debugLog(message: string, ...meta: any[]) {
if (!process.env.GQL_CODEGEN_NODEBUG && process.env.DEBUG !== undefined) {
queue.push({
message,
meta
meta,
});
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/typescript/react-apollo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"license": "MIT",
"scripts": {
"build": "tsc -m esnext --outDir dist/esnext && tsc -m commonjs --outDir dist/commonjs",
"test": "jest --config ../../../../jest.config.js"
"test": "jest --config ../../../../jest.config.js --forceExit"
},
"peerDependencies": {
"@types/graphql": "*",
Expand Down
48 changes: 27 additions & 21 deletions packages/plugins/typescript/react-apollo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { extname } from 'path';

export interface ReactApolloRawPluginConfig extends RawClientSideBasePluginConfig {
/**
* @name withHOC
* @name withComponent
* @type boolean
* @description Customized the output by enabling/disabling the HOC.
* @description Customized the output by enabling/disabling the generated Component.
* @default true
*
* @example
Expand All @@ -20,14 +20,14 @@ export interface ReactApolloRawPluginConfig extends RawClientSideBasePluginConfi
* - typescript-operations
* - typescript-react-apollo
* config:
* withHOC: false
* withComponent: false
* ```
*/
withHOC?: boolean;
withComponent?: boolean;
/**
* @name withComponent
* @name withHOC
* @type boolean
* @description Customized the output by enabling/disabling the generated Component.
* @description Customized the output by enabling/disabling the HOC.
* @default true
*
* @example
Expand All @@ -39,10 +39,10 @@ export interface ReactApolloRawPluginConfig extends RawClientSideBasePluginConfi
* - typescript-operations
* - typescript-react-apollo
* config:
* withComponent: false
* withHOC: false
* ```
*/
withComponent?: boolean;
withHOC?: boolean;
/**
* @name withHooks
* @type boolean
Expand Down Expand Up @@ -84,23 +84,29 @@ export interface ReactApolloRawPluginConfig extends RawClientSideBasePluginConfi
withMutationFn?: boolean;

/**
* @name hooksImportFrom
* @name apolloReactCommonImportFrom
* @type string
* @default @apollo/react-common
*/
apolloReactCommonImportFrom?: string;
/**
* @name apolloReactComponentsImportFrom
* @type string
* @default @apollo/react-components
*/
apolloReactComponentsImportFrom?: string;
/**
* @name apolloReactHocImportFrom
* @type string
* @description You can specify alternative module that is exports `useQuery` `useMutation` and `useSubscription`.
* This is useful for further abstraction of some common tasks (eg. error handling).
* Filepath relative to generated file can be also specified.
* @default react-apollo-hooks
* @default @apollo/react-hoc
*/
hooksImportFrom?: string;
apolloReactHocImportFrom?: string;
/**
* @name reactApolloImportFrom
* @name apolloReactHooksImportFrom
* @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 react-apollo
* @default @apollo/react-hooks
*/
reactApolloImportFrom?: string;
apolloReactHooksImportFrom?: string;
/**
* @name componentSuffix
* @type string
Expand Down Expand Up @@ -179,7 +185,7 @@ export const plugin: PluginFunction<ReactApolloRawPluginConfig> = (schema: Graph
...(config.externalFragments || []),
];

const visitor = new ReactApolloVisitor(allFragments, config, documents) as any;
const visitor = new ReactApolloVisitor(allFragments, config, documents);
const visitorResult = visit(allAst, { leave: visitor });

return {
Expand Down
129 changes: 75 additions & 54 deletions packages/plugins/typescript/react-apollo/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,38 @@ import { toPascalCase, Types } from '@graphql-codegen/plugin-helpers';
import { titleCase } from 'change-case';

export interface ReactApolloPluginConfig extends ClientSideBasePluginConfig {
withHOC: boolean;
withComponent: boolean;
withHOC: boolean;
withHooks: boolean;
withMutationFn: boolean;
hooksImportFrom: string;
reactApolloImportFrom: string;
apolloReactCommonImportFrom: string;
apolloReactComponentsImportFrom: string;
apolloReactHocImportFrom: string;
apolloReactHooksImportFrom: string;
componentSuffix: string;
reactApolloVersion: 2 | 3;
withResultType: boolean;
withMutationOptionsType: boolean;
}

export class ReactApolloVisitor extends ClientSideBaseVisitor<ReactApolloRawPluginConfig, ReactApolloPluginConfig> {
constructor(fragments: LoadedFragment[], rawConfig: ReactApolloRawPluginConfig, documents?: Types.DocumentFile[]) {
super(
fragments,
rawConfig,
{
componentSuffix: getConfigValue(rawConfig.componentSuffix, 'Component'),
withHOC: getConfigValue(rawConfig.withHOC, true),
withComponent: getConfigValue(rawConfig.withComponent, true),
withHooks: getConfigValue(rawConfig.withHooks, false),
withMutationFn: getConfigValue(rawConfig.withMutationFn, true),
hooksImportFrom: getConfigValue(rawConfig.hooksImportFrom, 'react-apollo-hooks'),
reactApolloImportFrom: getConfigValue(rawConfig.reactApolloImportFrom, 'react-apollo'),
reactApolloVersion: getConfigValue(rawConfig.reactApolloVersion, 2),
withResultType: getConfigValue(rawConfig.withResultType, true),
withMutationOptionsType: getConfigValue(rawConfig.withMutationOptionsType, true),
} as any,
documents
);
constructor(fragments: LoadedFragment[], rawConfig: ReactApolloRawPluginConfig, documents: Types.DocumentFile[]) {
super(fragments, rawConfig, {
componentSuffix: getConfigValue(rawConfig.componentSuffix, 'Component'),
withHOC: getConfigValue(rawConfig.withHOC, true),
withComponent: getConfigValue(rawConfig.withComponent, true),
withHooks: getConfigValue(rawConfig.withHooks, false),
withMutationFn: getConfigValue(rawConfig.withMutationFn, true),
apolloReactCommonImportFrom: getConfigValue(rawConfig.apolloReactCommonImportFrom, '@apollo/react-common'),
apolloReactComponentsImportFrom: getConfigValue(rawConfig.apolloReactComponentsImportFrom, '@apollo/react-components'),
apolloReactHocImportFrom: getConfigValue(rawConfig.apolloReactHocImportFrom, '@apollo/react-hoc'),
apolloReactHooksImportFrom: getConfigValue(rawConfig.apolloReactHooksImportFrom, '@apollo/react-hooks'),
reactApolloVersion: getConfigValue(rawConfig.reactApolloVersion, 2),
withResultType: getConfigValue(rawConfig.withResultType, true),
withMutationOptionsType: getConfigValue(rawConfig.withMutationOptionsType, true),
});

this._documents = documents;

autoBind(this);
}
Expand All @@ -47,12 +48,20 @@ export class ReactApolloVisitor extends ClientSideBaseVisitor<ReactApolloRawPlug
return `import * as React from 'react';`;
}

private getReactApolloImport(): string {
return `import * as ReactApollo from '${typeof this.config.reactApolloImportFrom === 'string' ? this.config.reactApolloImportFrom : 'react-apollo'}';`;
private getApolloReactCommonImport(): string {
return `import * as ApolloReactCommon from '${this.config.apolloReactCommonImportFrom}';`;
}

private getApolloReactComponentsImport(): string {
return `import * as ApolloReactComponents from '${this.config.apolloReactComponentsImportFrom}';`;
}

private getReactApolloHooksImport(): string {
return `import * as ReactApolloHooks from '${typeof this.config.hooksImportFrom === 'string' ? this.config.hooksImportFrom : 'react-apollo-hooks'}';`;
private getApolloReactHocImport(): string {
return `import * as ApolloReactHoc from '${this.config.apolloReactHocImportFrom}';`;
}

private getApolloReactHooksImport(): string {
return `import * as ApolloReactHooks from '${this.config.apolloReactHooksImportFrom}';`;
}

private getOmitDeclaration(): string {
Expand All @@ -75,31 +84,34 @@ export class ReactApolloVisitor extends ClientSideBaseVisitor<ReactApolloRawPlug
const variablesVarName = this.convertName(operationName + toPascalCase(operationType) + 'Variables');
const argType = operationType === 'mutation' ? 'MutateProps' : 'DataProps';

this.imports.add(this.getReactApolloImport());
this.imports.add(this.getApolloReactCommonImport());
this.imports.add(this.getApolloReactHocImport());

return `ReactApollo.${argType}<${typeVariableName}, ${variablesVarName}>`;
return `ApolloReactHoc.${argType}<${typeVariableName}, ${variablesVarName}>`;
}

private _buildMutationFn(node: OperationDefinitionNode, operationResultType: string, operationVariablesTypes: string): string {
if (node.operation === 'mutation') {
this.imports.add(this.getReactApolloImport());
return `export type ${this.convertName(node.name.value + 'MutationFn')} = ReactApollo.${MutationFnNameForVersionMap[this.config.reactApolloVersion]}<${operationResultType}, ${operationVariablesTypes}>;`;
this.imports.add(this.getApolloReactCommonImport());
return `export type ${this.convertName(node.name.value + 'MutationFn')} = ApolloReactCommon.MutationFunction<${operationResultType}, ${operationVariablesTypes}>;`;
}
return null;
}

private _buildOperationHoc(node: OperationDefinitionNode, documentVariableName: string, operationResultType: string, operationVariablesTypes: string): string {
this.imports.add(this.getApolloReactCommonImport());
this.imports.add(this.getApolloReactHocImport());
const operationName: string = this.convertName(node.name.value, { useTypesPrefix: false });
const propsTypeName: string = this.convertName(node.name.value, { suffix: 'Props' });

const propsVar = `export type ${propsTypeName}<TChildProps = {}> = ${this._buildHocProps(node.name.value, node.operation)} & TChildProps;`;

const hocString = `export function with${operationName}<TProps, TChildProps = {}>(operationOptions?: ReactApollo.OperationOption<
const hocString = `export function with${operationName}<TProps, TChildProps = {}>(operationOptions?: ApolloReactHoc.OperationOption<
TProps,
${operationResultType},
${operationVariablesTypes},
${propsTypeName}<TChildProps>>) {
return ReactApollo.with${titleCase(node.operation)}<TProps, ${operationResultType}, ${operationVariablesTypes}, ${propsTypeName}<TChildProps>>(${documentVariableName}, {
return ApolloReactHoc.with${titleCase(node.operation)}<TProps, ${operationResultType}, ${operationVariablesTypes}, ${propsTypeName}<TChildProps>>(${documentVariableName}, {
alias: 'with${operationName}',
...operationOptions
});
Expand All @@ -109,16 +121,23 @@ export class ReactApolloVisitor extends ClientSideBaseVisitor<ReactApolloRawPlug
}

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

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

this.imports.add(this.getReactImport());
this.imports.add(this.getReactApolloImport());
this.imports.add(this.getApolloReactCommonImport());
this.imports.add(this.getApolloReactComponentsImport());
this.imports.add(this.getOmitDeclaration());

const propsType = `Omit<ReactApollo.${operationType}Props<${operationResultType}, ${operationVariablesTypes}>, '${operationType.toLowerCase()}'>`;
const propsType = `Omit<ApolloReactComponents.${operationType}ComponentOptions<${operationResultType}, ${operationVariablesTypes}>, '${operationType.toLowerCase()}'>`;
let componentProps = '';
if (isVariablesRequired) {
componentProps = `export type ${componentPropsName} = ${propsType} & ({ variables: ${operationVariablesTypes}; skip?: boolean; } | { skip: boolean; });`;
Expand All @@ -128,20 +147,24 @@ export class ReactApolloVisitor extends ClientSideBaseVisitor<ReactApolloRawPlug

const component = `
export const ${componentName} = (props: ${componentPropsName}) => (
<ReactApollo.${operationType}<${operationResultType}, ${operationVariablesTypes}> ${node.operation}={${this.config.documentMode === DocumentMode.external ? `Operations.${node.name.value}` : documentVariableName}} {...props} />
<ApolloReactComponents.${operationType}<${operationResultType}, ${operationVariablesTypes}> ${node.operation}={${this.config.documentMode === DocumentMode.external ? `Operations.${node.name.value}` : documentVariableName}} {...props} />
);
`;
return [componentProps, component].join('\n');
}

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 });
const operationName: string = this.convertName(node.name.value, {
suffix: titleCase(operationType),
useTypesPrefix: false,
});

this.imports.add(this.getReactApolloHooksImport());
this.imports.add(this.getApolloReactCommonImport());
this.imports.add(this.getApolloReactHooksImport());

const hookFn = `
export function use${operationName}(baseOptions?: ReactApolloHooks.${operationType}HookOptions<${this.config.hooksImportFrom === '@apollo/react-hooks' || node.operation !== 'query' ? `${operationResultType}, ` : ''}${operationVariablesTypes}>) {
return ReactApolloHooks.use${operationType}<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, baseOptions);
export function use${operationName}(baseOptions?: ApolloReactHooks.${operationType}HookOptions<${operationResultType}, ${operationVariablesTypes}>) {
return ApolloReactHooks.use${operationType}<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, baseOptions);
};`;

const hookResult = `export type ${operationName}HookResult = ReturnType<typeof use${operationName}>;`;
Expand All @@ -150,18 +173,21 @@ export class ReactApolloVisitor extends ClientSideBaseVisitor<ReactApolloRawPlug
}

private _buildResultType(node: OperationDefinitionNode, operationType: string, operationResultType: string, operationVariablesTypes: string): string {
const componentResultType = this.convertName(node.name.value, { suffix: `${operationType}Result`, useTypesPrefix: false });
const componentResultType = this.convertName(node.name.value, {
suffix: `${operationType}Result`,
useTypesPrefix: false,
});

switch (node.operation) {
case 'query':
this.imports.add(this.getReactApolloImport());
return `export type ${componentResultType} = ReactApollo.QueryResult<${operationResultType}, ${operationVariablesTypes}>;`;
this.imports.add(this.getApolloReactCommonImport());
return `export type ${componentResultType} = ApolloReactCommon.QueryResult<${operationResultType}, ${operationVariablesTypes}>;`;
case 'mutation':
this.imports.add(this.getReactApolloImport());
return `export type ${componentResultType} = ReactApollo.MutationResult<${operationResultType}>;`;
this.imports.add(this.getApolloReactCommonImport());
return `export type ${componentResultType} = ApolloReactCommon.MutationResult<${operationResultType}>;`;
case 'subscription':
this.imports.add(this.getReactApolloImport());
return `export type ${componentResultType} = ReactApollo.SubscriptionResult<${operationResultType}>;`;
this.imports.add(this.getApolloReactCommonImport());
return `export type ${componentResultType} = ApolloReactCommon.SubscriptionResult<${operationResultType}>;`;
default:
return '';
}
Expand All @@ -172,11 +198,11 @@ export class ReactApolloVisitor extends ClientSideBaseVisitor<ReactApolloRawPlug
return '';
}

this.imports.add(this.getReactApolloImport());
this.imports.add(this.getApolloReactCommonImport());

const mutationOptionsType = this.convertName(node.name.value, { suffix: 'MutationOptions', useTypesPrefix: false });

return `export type ${mutationOptionsType} = ReactApollo.MutationOptions<${operationResultType}, ${operationVariablesTypes}>;`;
return `export type ${mutationOptionsType} = ApolloReactCommon.BaseMutationOptions<${operationResultType}, ${operationVariablesTypes}>;`;
}

protected buildOperation(node: OperationDefinitionNode, documentVariableName: string, operationType: string, operationResultType: string, operationVariablesTypes: string): string {
Expand All @@ -190,8 +216,3 @@ export class ReactApolloVisitor extends ClientSideBaseVisitor<ReactApolloRawPlug
return [mutationFn, component, hoc, hooks, resultType, mutationOptionsType].filter(a => a).join('\n');
}
}

const MutationFnNameForVersionMap = {
2: 'MutationFn',
3: 'MutationFunction',
};

0 comments on commit adf605b

Please sign in to comment.