UNPKG

@graphql-codegen/typescript-react-apollo

Version:

GraphQL Code Generator plugin for generating a ready-to-use React Components/HOC/Hooks based on GraphQL operations

405 lines (404 loc) • 23.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReactApolloVisitor = void 0; const tslib_1 = require("tslib"); const auto_bind_1 = tslib_1.__importDefault(require("auto-bind")); const change_case_all_1 = require("change-case-all"); const graphql_1 = require("graphql"); const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common"); const APOLLO_CLIENT_3_UNIFIED_PACKAGE = `@apollo/client`; const GROUPED_APOLLO_CLIENT_3_IDENTIFIER = 'Apollo'; function hasRequiredVariables(node) { var _a, _b; return ((_b = (_a = node.variableDefinitions) === null || _a === void 0 ? void 0 : _a.some(variableDef => variableDef.type.kind === graphql_1.Kind.NON_NULL_TYPE && !variableDef.defaultValue)) !== null && _b !== void 0 ? _b : false); } class ReactApolloVisitor extends visitor_plugin_common_1.ClientSideBaseVisitor { constructor(schema, fragments, rawConfig, documents) { super(schema, fragments, rawConfig, { componentSuffix: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.componentSuffix, 'Component'), withHOC: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.withHOC, false), withComponent: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.withComponent, false), withHooks: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.withHooks, true), withMutationFn: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.withMutationFn, true), withRefetchFn: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.withRefetchFn, false), withFragmentHooks: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.withFragmentHooks, false), apolloReactCommonImportFrom: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.apolloReactCommonImportFrom, rawConfig.reactApolloVersion === 2 ? '@apollo/react-common' : APOLLO_CLIENT_3_UNIFIED_PACKAGE), apolloReactComponentsImportFrom: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.apolloReactComponentsImportFrom, rawConfig.reactApolloVersion === 2 ? '@apollo/react-components' : `${APOLLO_CLIENT_3_UNIFIED_PACKAGE}/react/components`), apolloReactHocImportFrom: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.apolloReactHocImportFrom, rawConfig.reactApolloVersion === 2 ? '@apollo/react-hoc' : `${APOLLO_CLIENT_3_UNIFIED_PACKAGE}/react/hoc`), apolloReactHooksImportFrom: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.apolloReactHooksImportFrom, rawConfig.reactApolloVersion === 2 ? '@apollo/react-hooks' : APOLLO_CLIENT_3_UNIFIED_PACKAGE), reactApolloVersion: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.reactApolloVersion, 3), withResultType: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.withResultType, true), withMutationOptionsType: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.withMutationOptionsType, true), addDocBlocks: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.addDocBlocks, true), defaultBaseOptions: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.defaultBaseOptions, {}), gqlImport: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.gqlImport, rawConfig.reactApolloVersion === 2 ? null : `${APOLLO_CLIENT_3_UNIFIED_PACKAGE}#gql`), hooksSuffix: (0, visitor_plugin_common_1.getConfigValue)(rawConfig.hooksSuffix, ''), }); this.rawConfig = rawConfig; this.imports = new Set(); this._externalImportPrefix = this.config.importOperationTypesFrom ? `${this.config.importOperationTypesFrom}.` : ''; this._documents = documents; (0, auto_bind_1.default)(this); } getImportStatement(isTypeImport) { return isTypeImport && this.config.useTypeImports ? 'import type' : 'import'; } getReactImport() { return `import * as React from 'react';`; } getApolloReactCommonIdentifier() { if (this.rawConfig.apolloReactCommonImportFrom || this.config.reactApolloVersion === 2) { return `ApolloReactCommon`; } return GROUPED_APOLLO_CLIENT_3_IDENTIFIER; } getApolloReactHooksIdentifier() { if (this.rawConfig.apolloReactHooksImportFrom || this.config.reactApolloVersion === 2) { return `ApolloReactHooks`; } return GROUPED_APOLLO_CLIENT_3_IDENTIFIER; } usesExternalHooksOnly() { const apolloReactCommonIdentifier = this.getApolloReactCommonIdentifier(); return (apolloReactCommonIdentifier === GROUPED_APOLLO_CLIENT_3_IDENTIFIER && this.config.apolloReactHooksImportFrom !== APOLLO_CLIENT_3_UNIFIED_PACKAGE && this.config.withHooks && !this.config.withComponent && !this.config.withHOC); } getApolloReactCommonImport(isTypeImport) { const apolloReactCommonIdentifier = this.getApolloReactCommonIdentifier(); return `${this.getImportStatement(isTypeImport && (apolloReactCommonIdentifier !== GROUPED_APOLLO_CLIENT_3_IDENTIFIER || this.usesExternalHooksOnly()))} * as ${apolloReactCommonIdentifier} from '${this.config.apolloReactCommonImportFrom}';`; } getApolloReactComponentsImport(isTypeImport) { return `${this.getImportStatement(isTypeImport)} * as ApolloReactComponents from '${this.config.apolloReactComponentsImportFrom}';`; } getApolloReactHocImport(isTypeImport) { return `${this.getImportStatement(isTypeImport)} * as ApolloReactHoc from '${this.config.apolloReactHocImportFrom}';`; } getApolloReactHooksImport(isTypeImport) { return `${this.getImportStatement(isTypeImport)} * as ${this.getApolloReactHooksIdentifier()} from '${this.config.apolloReactHooksImportFrom}';`; } getOmitDeclaration() { return visitor_plugin_common_1.OMIT_TYPE; } getDefaultOptions() { return `const defaultOptions = ${JSON.stringify(this.config.defaultBaseOptions)} as const;`; } getDocumentNodeVariable(node, documentVariableName) { var _a, _b; return this.config.documentMode === visitor_plugin_common_1.DocumentMode.external ? `Operations.${(_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ''}` : documentVariableName; } getImports() { const baseImports = super.getImports(); const hasOperations = this._collectedOperations.length > 0; if (!hasOperations && !this.config.withFragmentHooks) { return baseImports; } if (this.config.withFragmentHooks) { return [...baseImports, this.getApolloReactHooksImport(false), ...Array.from(this.imports)]; } return [...baseImports, ...Array.from(this.imports)]; } _buildHocProps(operationName, operationType) { const typeVariableName = this._externalImportPrefix + this.convertName(operationName + (0, change_case_all_1.pascalCase)(operationType) + this._parsedConfig.operationResultSuffix); const variablesVarName = this._externalImportPrefix + this.convertName(operationName + (0, change_case_all_1.pascalCase)(operationType) + 'Variables'); const typeArgs = `<${typeVariableName}, ${variablesVarName}>`; if (operationType === 'mutation') { this.imports.add(this.getApolloReactCommonImport(true)); return `${this.getApolloReactCommonIdentifier()}.MutationFunction${typeArgs}`; } this.imports.add(this.getApolloReactHocImport(true)); return `ApolloReactHoc.DataValue${typeArgs}`; } _buildMutationFn(node, operationResultType, operationVariablesTypes) { var _a, _b; if (node.operation === 'mutation') { this.imports.add(this.getApolloReactCommonImport(true)); return `export type ${this.convertName(((_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '') + 'MutationFn')} = ${this.getApolloReactCommonIdentifier()}.MutationFunction<${operationResultType}, ${operationVariablesTypes}>;`; } return null; } _buildOperationHoc(node, documentVariableName, operationResultType, operationVariablesTypes) { var _a, _b; this.imports.add(this.getApolloReactCommonImport(false)); this.imports.add(this.getApolloReactHocImport(false)); const nodeName = (_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ''; const operationName = this.convertName(nodeName, { useTypesPrefix: false }); const propsTypeName = this.convertName(nodeName, { suffix: 'Props' }); const defaultDataName = node.operation === 'mutation' ? 'mutate' : 'data'; const propsVar = `export type ${propsTypeName}<TChildProps = {}, TDataName extends string = '${defaultDataName}'> = { [key in TDataName]: ${this._buildHocProps(nodeName, node.operation)} } & TChildProps;`; const hocString = `export function with${operationName}<TProps, TChildProps = {}, TDataName extends string = '${defaultDataName}'>(operationOptions?: ApolloReactHoc.OperationOption< TProps, ${operationResultType}, ${operationVariablesTypes}, ${propsTypeName}<TChildProps, TDataName>>) { return ApolloReactHoc.with${(0, change_case_all_1.pascalCase)(node.operation)}<TProps, ${operationResultType}, ${operationVariablesTypes}, ${propsTypeName}<TChildProps, TDataName>>(${this.getDocumentNodeVariable(node, documentVariableName)}, { alias: '${(0, change_case_all_1.camelCase)(operationName)}', ...operationOptions }); };`; return [propsVar, hocString].filter(a => a).join('\n'); } _buildComponent(node, documentVariableName, operationType, operationResultType, operationVariablesTypes) { var _a, _b; const nodeName = (_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ''; const componentPropsName = this.convertName(nodeName, { suffix: this.config.componentSuffix + 'Props', useTypesPrefix: false, }); const componentName = this.convertName(nodeName, { suffix: this.config.componentSuffix, useTypesPrefix: false, }); const isVariablesRequired = operationType === 'Query' && hasRequiredVariables(node); this.imports.add(this.getReactImport()); this.imports.add(this.getApolloReactCommonImport(true)); this.imports.add(this.getApolloReactComponentsImport(false)); this.imports.add(this.getOmitDeclaration()); 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; });`; } else { componentProps = `export type ${componentPropsName} = ${propsType};`; } const component = ` export const ${componentName} = (props: ${componentPropsName}) => ( <ApolloReactComponents.${operationType}<${operationResultType}, ${operationVariablesTypes}> ${node.operation}={${this.getDocumentNodeVariable(node, documentVariableName)}} {...props} /> ); `; return [componentProps, component].join('\n'); } _buildHooksJSDoc(node, operationName, operationType) { const variableString = node.variableDefinitions.reduce((acc, item) => { const name = item.variable.name.value; return `${acc}\n * ${name}: // value for '${name}'`; }, ''); const queryDescription = ` * To run a query within a React component, call \`use${operationName}\` and pass it any options that fit your needs. * When your component renders, \`use${operationName}\` returns an object from Apollo Client that contains loading, error, and data properties * you can use to render your UI.`; const queryExample = ` * const { data, loading, error } = use${operationName}({ * variables: {${variableString} * }, * });`; const mutationDescription = ` * To run a mutation, you first call \`use${operationName}\` within a React component and pass it any options that fit your needs. * When your component renders, \`use${operationName}\` returns a tuple that includes: * - A mutate function that you can call at any time to execute the mutation * - An object with fields that represent the current status of the mutation's execution`; const mutationExample = ` * const [${(0, change_case_all_1.camelCase)(operationName)}, { data, loading, error }] = use${operationName}({ * variables: {${variableString} * }, * });`; return ` /** * __use${operationName}__ *${operationType === 'Mutation' ? mutationDescription : queryDescription} * * @param baseOptions options that will be passed into the ${operationType.toLowerCase()}, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#${operationType === 'Mutation' ? 'options-2' : 'options'}; * * @example${operationType === 'Mutation' ? mutationExample : queryExample} */`; } _buildHooks(node, operationType, documentVariableName, operationResultType, operationVariablesTypes, hasRequiredVariables) { var _a, _b; const nodeName = (_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ''; const suffix = this._getHookSuffix(nodeName, operationType); const shouldEnforceRequiredVariables = hasRequiredVariables && operationType !== 'Mutation'; const operationName = this.convertName(nodeName, { suffix, useTypesPrefix: false, useTypesSuffix: false, }) + this.config.hooksSuffix; this.imports.add(this.getApolloReactCommonImport(true)); this.imports.add(this.getApolloReactHooksImport(false)); this.imports.add(this.getDefaultOptions()); const hookFns = [ `export function use${operationName}(baseOptions${shouldEnforceRequiredVariables ? '' : '?'}: ${this.getApolloReactHooksIdentifier()}.${operationType}HookOptions<${operationResultType}, ${operationVariablesTypes}>${!shouldEnforceRequiredVariables ? '' : ` & ({ variables: ${operationVariablesTypes}; skip?: boolean; } | { skip: boolean; }) `}) { const options = {...defaultOptions, ...baseOptions} return ${this.getApolloReactHooksIdentifier()}.use${operationType}<${operationResultType}, ${operationVariablesTypes}>(${this.getDocumentNodeVariable(node, documentVariableName)}, options); }`, ]; if (this.config.addDocBlocks) { hookFns.unshift(this._buildHooksJSDoc(node, operationName, operationType)); } const hookResults = [ `export type ${operationName}HookResult = ReturnType<typeof use${operationName}>;`, ]; if (operationType === 'Query') { const lazyOperationName = this.convertName(nodeName, { suffix: (0, change_case_all_1.pascalCase)('LazyQuery'), useTypesPrefix: false, }) + this.config.hooksSuffix; hookFns.push(`export function use${lazyOperationName}(baseOptions?: ${this.getApolloReactHooksIdentifier()}.LazyQueryHookOptions<${operationResultType}, ${operationVariablesTypes}>) { const options = {...defaultOptions, ...baseOptions} return ${this.getApolloReactHooksIdentifier()}.useLazyQuery<${operationResultType}, ${operationVariablesTypes}>(${this.getDocumentNodeVariable(node, documentVariableName)}, options); }`); hookResults.push(`export type ${lazyOperationName}HookResult = ReturnType<typeof use${lazyOperationName}>;`); const suspenseOperationName = this.convertName(nodeName, { suffix: (0, change_case_all_1.pascalCase)('SuspenseQuery'), useTypesPrefix: false, }) + this.config.hooksSuffix; hookFns.push(`export function use${suspenseOperationName}(baseOptions?: ${this.getApolloReactHooksIdentifier()}.SuspenseQueryHookOptions<${operationResultType}, ${operationVariablesTypes}>) { const options = {...defaultOptions, ...baseOptions} return ${this.getApolloReactHooksIdentifier()}.useSuspenseQuery<${operationResultType}, ${operationVariablesTypes}>(${this.getDocumentNodeVariable(node, documentVariableName)}, options); }`); hookResults.push(`export type ${suspenseOperationName}HookResult = ReturnType<typeof use${suspenseOperationName}>;`); } return [...hookFns, ...hookResults].join('\n'); } _getHookSuffix(name, operationType) { if (this.config.omitOperationSuffix) { return ''; } if (!this.config.dedupeOperationSuffix) { return (0, change_case_all_1.pascalCase)(operationType); } if (name.includes('Query') || name.includes('Mutation') || name.includes('Subscription')) { return ''; } return (0, change_case_all_1.pascalCase)(operationType); } _buildResultType(node, operationType, operationResultType, operationVariablesTypes) { var _a, _b; const componentResultType = this.convertName((_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '', { suffix: `${operationType}Result`, useTypesPrefix: false, }); switch (node.operation) { case 'query': this.imports.add(this.getApolloReactCommonImport(true)); return `export type ${componentResultType} = ${this.getApolloReactCommonIdentifier()}.QueryResult<${operationResultType}, ${operationVariablesTypes}>;`; case 'mutation': this.imports.add(this.getApolloReactCommonImport(true)); return `export type ${componentResultType} = ${this.getApolloReactCommonIdentifier()}.MutationResult<${operationResultType}>;`; case 'subscription': this.imports.add(this.getApolloReactCommonImport(true)); return `export type ${componentResultType} = ${this.getApolloReactCommonIdentifier()}.SubscriptionResult<${operationResultType}>;`; default: return ''; } } _buildWithMutationOptionsType(node, operationResultType, operationVariablesTypes) { var _a, _b; if (node.operation !== 'mutation') { return ''; } this.imports.add(this.getApolloReactCommonImport(true)); const mutationOptionsType = this.convertName((_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '', { suffix: 'MutationOptions', useTypesPrefix: false, }); return `export type ${mutationOptionsType} = ${this.getApolloReactCommonIdentifier()}.BaseMutationOptions<${operationResultType}, ${operationVariablesTypes}>;`; } _buildRefetchFn(node, documentVariableName, operationType, operationVariablesTypes) { var _a, _b; if (node.operation !== 'query') { return ''; } const nodeName = (_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ''; const operationName = this.convertName(nodeName, { suffix: this._getHookSuffix(nodeName, operationType), useTypesPrefix: false, }) + this.config.hooksSuffix; const optional = hasRequiredVariables(node) ? '' : '?'; return `export function refetch${operationName}(variables${optional}: ${operationVariablesTypes}) { return { query: ${this.getDocumentNodeVariable(node, documentVariableName)}, variables: variables } }`; } buildOperation(node, documentVariableName, operationType, operationResultType, operationVariablesTypes, hasRequiredVariables) { operationResultType = this._externalImportPrefix + operationResultType; operationVariablesTypes = this._externalImportPrefix + operationVariablesTypes; const mutationFn = this.config.withMutationFn || this.config.withComponent ? this._buildMutationFn(node, operationResultType, operationVariablesTypes) : null; const component = this.config.withComponent ? this._buildComponent(node, documentVariableName, operationType, operationResultType, operationVariablesTypes) : null; const hoc = this.config.withHOC ? this._buildOperationHoc(node, documentVariableName, operationResultType, operationVariablesTypes) : null; const hooks = this.config.withHooks ? this._buildHooks(node, operationType, documentVariableName, operationResultType, operationVariablesTypes, hasRequiredVariables) : null; const resultType = this.config.withResultType ? this._buildResultType(node, operationType, operationResultType, operationVariablesTypes) : null; const mutationOptionsType = this.config.withMutationOptionsType ? this._buildWithMutationOptionsType(node, operationResultType, operationVariablesTypes) : null; const refetchFn = this.config.withRefetchFn ? this._buildRefetchFn(node, documentVariableName, operationType, operationVariablesTypes) : null; return [mutationFn, component, hoc, hooks, resultType, mutationOptionsType, refetchFn] .filter(a => a) .join('\n'); } get fragments() { var _a, _b; const fragments = super.fragments; if (this._fragments.length === 0 || !this.config.withFragmentHooks) { return fragments; } const operationType = 'Fragment'; const hookFns = [fragments]; for (const fragment of this._fragments.values()) { if (fragment.isExternal) { continue; } const nodeName = (_a = fragment.name) !== null && _a !== void 0 ? _a : ''; const suffix = this._getHookSuffix(nodeName, operationType); const fragmentName = this.convertName(nodeName, { suffix, useTypesPrefix: false, useTypesSuffix: false, }) + this.config.hooksSuffix; const operationTypeSuffix = this.getOperationSuffix(fragmentName, operationType); const operationResultType = this.convertName(nodeName, { suffix: operationTypeSuffix + this._parsedConfig.operationResultSuffix, }); const IDType = (_b = this.scalars.ID) !== null && _b !== void 0 ? _b : 'string'; const hook = `export function use${fragmentName}<F = { id: ${IDType} }>(identifiers: F) { return ${this.getApolloReactHooksIdentifier()}.use${operationType}<${operationResultType}>({ fragment: ${nodeName}${this.config.fragmentVariableSuffix}, fragmentName: "${nodeName}", from: { __typename: "${fragment.onType}", ...identifiers, }, }); }`; const hookResults = [ `export type ${fragmentName}HookResult = ReturnType<typeof use${fragmentName}>;`, ]; hookFns.push([hook, hookResults].join('\n')); } return hookFns.join('\n'); } } exports.ReactApolloVisitor = ReactApolloVisitor;