UNPKG

22.9 kBJavaScriptView Raw
1import autoBind from 'auto-bind';
2import { camelCase, pascalCase } from 'change-case-all';
3import { Kind } from 'graphql';
4import { ClientSideBaseVisitor, DocumentMode, getConfigValue, OMIT_TYPE, } from '@graphql-codegen/visitor-plugin-common';
5const APOLLO_CLIENT_3_UNIFIED_PACKAGE = `@apollo/client`;
6const GROUPED_APOLLO_CLIENT_3_IDENTIFIER = 'Apollo';
7function hasRequiredVariables(node) {
8 var _a, _b;
9 return ((_b = (_a = node.variableDefinitions) === null || _a === void 0 ? void 0 : _a.some(variableDef => variableDef.type.kind === Kind.NON_NULL_TYPE && !variableDef.defaultValue)) !== null && _b !== void 0 ? _b : false);
10}
11export class ReactApolloVisitor extends ClientSideBaseVisitor {
12 constructor(schema, fragments, rawConfig, documents) {
13 super(schema, fragments, rawConfig, {
14 componentSuffix: getConfigValue(rawConfig.componentSuffix, 'Component'),
15 withHOC: getConfigValue(rawConfig.withHOC, false),
16 withComponent: getConfigValue(rawConfig.withComponent, false),
17 withHooks: getConfigValue(rawConfig.withHooks, true),
18 withMutationFn: getConfigValue(rawConfig.withMutationFn, true),
19 withRefetchFn: getConfigValue(rawConfig.withRefetchFn, false),
20 withFragmentHooks: getConfigValue(rawConfig.withFragmentHooks, false),
21 apolloReactCommonImportFrom: getConfigValue(rawConfig.apolloReactCommonImportFrom, rawConfig.reactApolloVersion === 2
22 ? '@apollo/react-common'
23 : APOLLO_CLIENT_3_UNIFIED_PACKAGE),
24 apolloReactComponentsImportFrom: getConfigValue(rawConfig.apolloReactComponentsImportFrom, rawConfig.reactApolloVersion === 2
25 ? '@apollo/react-components'
26 : `${APOLLO_CLIENT_3_UNIFIED_PACKAGE}/react/components`),
27 apolloReactHocImportFrom: getConfigValue(rawConfig.apolloReactHocImportFrom, rawConfig.reactApolloVersion === 2
28 ? '@apollo/react-hoc'
29 : `${APOLLO_CLIENT_3_UNIFIED_PACKAGE}/react/hoc`),
30 apolloReactHooksImportFrom: getConfigValue(rawConfig.apolloReactHooksImportFrom, rawConfig.reactApolloVersion === 2
31 ? '@apollo/react-hooks'
32 : APOLLO_CLIENT_3_UNIFIED_PACKAGE),
33 reactApolloVersion: getConfigValue(rawConfig.reactApolloVersion, 3),
34 withResultType: getConfigValue(rawConfig.withResultType, true),
35 withMutationOptionsType: getConfigValue(rawConfig.withMutationOptionsType, true),
36 addDocBlocks: getConfigValue(rawConfig.addDocBlocks, true),
37 defaultBaseOptions: getConfigValue(rawConfig.defaultBaseOptions, {}),
38 gqlImport: getConfigValue(rawConfig.gqlImport, rawConfig.reactApolloVersion === 2 ? null : `${APOLLO_CLIENT_3_UNIFIED_PACKAGE}#gql`),
39 hooksSuffix: getConfigValue(rawConfig.hooksSuffix, ''),
40 });
41 this.rawConfig = rawConfig;
42 this.imports = new Set();
43 this._externalImportPrefix = this.config.importOperationTypesFrom
44 ? `${this.config.importOperationTypesFrom}.`
45 : '';
46 this._documents = documents;
47 autoBind(this);
48 }
49 getImportStatement(isTypeImport) {
50 return isTypeImport && this.config.useTypeImports ? 'import type' : 'import';
51 }
52 getReactImport() {
53 return `import * as React from 'react';`;
54 }
55 getApolloReactCommonIdentifier() {
56 if (this.rawConfig.apolloReactCommonImportFrom || this.config.reactApolloVersion === 2) {
57 return `ApolloReactCommon`;
58 }
59 return GROUPED_APOLLO_CLIENT_3_IDENTIFIER;
60 }
61 getApolloReactHooksIdentifier() {
62 if (this.rawConfig.apolloReactHooksImportFrom || this.config.reactApolloVersion === 2) {
63 return `ApolloReactHooks`;
64 }
65 return GROUPED_APOLLO_CLIENT_3_IDENTIFIER;
66 }
67 usesExternalHooksOnly() {
68 const apolloReactCommonIdentifier = this.getApolloReactCommonIdentifier();
69 return (apolloReactCommonIdentifier === GROUPED_APOLLO_CLIENT_3_IDENTIFIER &&
70 this.config.apolloReactHooksImportFrom !== APOLLO_CLIENT_3_UNIFIED_PACKAGE &&
71 this.config.withHooks &&
72 !this.config.withComponent &&
73 !this.config.withHOC);
74 }
75 getApolloReactCommonImport(isTypeImport) {
76 const apolloReactCommonIdentifier = this.getApolloReactCommonIdentifier();
77 return `${this.getImportStatement(isTypeImport &&
78 (apolloReactCommonIdentifier !== GROUPED_APOLLO_CLIENT_3_IDENTIFIER ||
79 this.usesExternalHooksOnly()))} * as ${apolloReactCommonIdentifier} from '${this.config.apolloReactCommonImportFrom}';`;
80 }
81 getApolloReactComponentsImport(isTypeImport) {
82 return `${this.getImportStatement(isTypeImport)} * as ApolloReactComponents from '${this.config.apolloReactComponentsImportFrom}';`;
83 }
84 getApolloReactHocImport(isTypeImport) {
85 return `${this.getImportStatement(isTypeImport)} * as ApolloReactHoc from '${this.config.apolloReactHocImportFrom}';`;
86 }
87 getApolloReactHooksImport(isTypeImport) {
88 return `${this.getImportStatement(isTypeImport)} * as ${this.getApolloReactHooksIdentifier()} from '${this.config.apolloReactHooksImportFrom}';`;
89 }
90 getOmitDeclaration() {
91 return OMIT_TYPE;
92 }
93 getDefaultOptions() {
94 return `const defaultOptions = ${JSON.stringify(this.config.defaultBaseOptions)} as const;`;
95 }
96 getDocumentNodeVariable(node, documentVariableName) {
97 var _a, _b;
98 return this.config.documentMode === DocumentMode.external
99 ? `Operations.${(_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ''}`
100 : documentVariableName;
101 }
102 getImports() {
103 const baseImports = super.getImports();
104 const hasOperations = this._collectedOperations.length > 0;
105 if (!hasOperations && !this.config.withFragmentHooks) {
106 return baseImports;
107 }
108 if (this.config.withFragmentHooks) {
109 return [...baseImports, this.getApolloReactHooksImport(false), ...Array.from(this.imports)];
110 }
111 return [...baseImports, ...Array.from(this.imports)];
112 }
113 _buildHocProps(operationName, operationType) {
114 const typeVariableName = this._externalImportPrefix +
115 this.convertName(operationName + pascalCase(operationType) + this._parsedConfig.operationResultSuffix);
116 const variablesVarName = this._externalImportPrefix +
117 this.convertName(operationName + pascalCase(operationType) + 'Variables');
118 const typeArgs = `<${typeVariableName}, ${variablesVarName}>`;
119 if (operationType === 'mutation') {
120 this.imports.add(this.getApolloReactCommonImport(true));
121 return `${this.getApolloReactCommonIdentifier()}.MutationFunction${typeArgs}`;
122 }
123 this.imports.add(this.getApolloReactHocImport(true));
124 return `ApolloReactHoc.DataValue${typeArgs}`;
125 }
126 _buildMutationFn(node, operationResultType, operationVariablesTypes) {
127 var _a, _b;
128 if (node.operation === 'mutation') {
129 this.imports.add(this.getApolloReactCommonImport(true));
130 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}>;`;
131 }
132 return null;
133 }
134 _buildOperationHoc(node, documentVariableName, operationResultType, operationVariablesTypes) {
135 var _a, _b;
136 this.imports.add(this.getApolloReactCommonImport(false));
137 this.imports.add(this.getApolloReactHocImport(false));
138 const nodeName = (_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
139 const operationName = this.convertName(nodeName, { useTypesPrefix: false });
140 const propsTypeName = this.convertName(nodeName, { suffix: 'Props' });
141 const defaultDataName = node.operation === 'mutation' ? 'mutate' : 'data';
142 const propsVar = `export type ${propsTypeName}<TChildProps = {}, TDataName extends string = '${defaultDataName}'> = {
143 [key in TDataName]: ${this._buildHocProps(nodeName, node.operation)}
144 } & TChildProps;`;
145 const hocString = `export function with${operationName}<TProps, TChildProps = {}, TDataName extends string = '${defaultDataName}'>(operationOptions?: ApolloReactHoc.OperationOption<
146 TProps,
147 ${operationResultType},
148 ${operationVariablesTypes},
149 ${propsTypeName}<TChildProps, TDataName>>) {
150 return ApolloReactHoc.with${pascalCase(node.operation)}<TProps, ${operationResultType}, ${operationVariablesTypes}, ${propsTypeName}<TChildProps, TDataName>>(${this.getDocumentNodeVariable(node, documentVariableName)}, {
151 alias: '${camelCase(operationName)}',
152 ...operationOptions
153 });
154};`;
155 return [propsVar, hocString].filter(a => a).join('\n');
156 }
157 _buildComponent(node, documentVariableName, operationType, operationResultType, operationVariablesTypes) {
158 var _a, _b;
159 const nodeName = (_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
160 const componentPropsName = this.convertName(nodeName, {
161 suffix: this.config.componentSuffix + 'Props',
162 useTypesPrefix: false,
163 });
164 const componentName = this.convertName(nodeName, {
165 suffix: this.config.componentSuffix,
166 useTypesPrefix: false,
167 });
168 const isVariablesRequired = operationType === 'Query' && hasRequiredVariables(node);
169 this.imports.add(this.getReactImport());
170 this.imports.add(this.getApolloReactCommonImport(true));
171 this.imports.add(this.getApolloReactComponentsImport(false));
172 this.imports.add(this.getOmitDeclaration());
173 const propsType = `Omit<ApolloReactComponents.${operationType}ComponentOptions<${operationResultType}, ${operationVariablesTypes}>, '${operationType.toLowerCase()}'>`;
174 let componentProps = '';
175 if (isVariablesRequired) {
176 componentProps = `export type ${componentPropsName} = ${propsType} & ({ variables: ${operationVariablesTypes}; skip?: boolean; } | { skip: boolean; });`;
177 }
178 else {
179 componentProps = `export type ${componentPropsName} = ${propsType};`;
180 }
181 const component = `
182 export const ${componentName} = (props: ${componentPropsName}) => (
183 <ApolloReactComponents.${operationType}<${operationResultType}, ${operationVariablesTypes}> ${node.operation}={${this.getDocumentNodeVariable(node, documentVariableName)}} {...props} />
184 );
185 `;
186 return [componentProps, component].join('\n');
187 }
188 _buildHooksJSDoc(node, operationName, operationType) {
189 const variableString = node.variableDefinitions.reduce((acc, item) => {
190 const name = item.variable.name.value;
191 return `${acc}\n * ${name}: // value for '${name}'`;
192 }, '');
193 const queryDescription = `
194 * To run a query within a React component, call \`use${operationName}\` and pass it any options that fit your needs.
195 * When your component renders, \`use${operationName}\` returns an object from Apollo Client that contains loading, error, and data properties
196 * you can use to render your UI.`;
197 const queryExample = `
198 * const { data, loading, error } = use${operationName}({
199 * variables: {${variableString}
200 * },
201 * });`;
202 const mutationDescription = `
203 * To run a mutation, you first call \`use${operationName}\` within a React component and pass it any options that fit your needs.
204 * When your component renders, \`use${operationName}\` returns a tuple that includes:
205 * - A mutate function that you can call at any time to execute the mutation
206 * - An object with fields that represent the current status of the mutation's execution`;
207 const mutationExample = `
208 * const [${camelCase(operationName)}, { data, loading, error }] = use${operationName}({
209 * variables: {${variableString}
210 * },
211 * });`;
212 return `
213/**
214 * __use${operationName}__
215 *${operationType === 'Mutation' ? mutationDescription : queryDescription}
216 *
217 * @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'};
218 *
219 * @example${operationType === 'Mutation' ? mutationExample : queryExample}
220 */`;
221 }
222 _buildHooks(node, operationType, documentVariableName, operationResultType, operationVariablesTypes, hasRequiredVariables) {
223 var _a, _b;
224 const nodeName = (_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
225 const suffix = this._getHookSuffix(nodeName, operationType);
226 const shouldEnforceRequiredVariables = hasRequiredVariables && operationType !== 'Mutation';
227 const operationName = this.convertName(nodeName, {
228 suffix,
229 useTypesPrefix: false,
230 useTypesSuffix: false,
231 }) + this.config.hooksSuffix;
232 this.imports.add(this.getApolloReactCommonImport(true));
233 this.imports.add(this.getApolloReactHooksImport(false));
234 this.imports.add(this.getDefaultOptions());
235 const hookFns = [
236 `export function use${operationName}(baseOptions${shouldEnforceRequiredVariables ? '' : '?'}: ${this.getApolloReactHooksIdentifier()}.${operationType}HookOptions<${operationResultType}, ${operationVariablesTypes}>${!shouldEnforceRequiredVariables
237 ? ''
238 : ` & ({ variables: ${operationVariablesTypes}; skip?: boolean; } | { skip: boolean; }) `}) {
239 const options = {...defaultOptions, ...baseOptions}
240 return ${this.getApolloReactHooksIdentifier()}.use${operationType}<${operationResultType}, ${operationVariablesTypes}>(${this.getDocumentNodeVariable(node, documentVariableName)}, options);
241 }`,
242 ];
243 if (this.config.addDocBlocks) {
244 hookFns.unshift(this._buildHooksJSDoc(node, operationName, operationType));
245 }
246 const hookResults = [
247 `export type ${operationName}HookResult = ReturnType<typeof use${operationName}>;`,
248 ];
249 if (operationType === 'Query') {
250 const lazyOperationName = this.convertName(nodeName, {
251 suffix: pascalCase('LazyQuery'),
252 useTypesPrefix: false,
253 }) + this.config.hooksSuffix;
254 hookFns.push(`export function use${lazyOperationName}(baseOptions?: ${this.getApolloReactHooksIdentifier()}.LazyQueryHookOptions<${operationResultType}, ${operationVariablesTypes}>) {
255 const options = {...defaultOptions, ...baseOptions}
256 return ${this.getApolloReactHooksIdentifier()}.useLazyQuery<${operationResultType}, ${operationVariablesTypes}>(${this.getDocumentNodeVariable(node, documentVariableName)}, options);
257 }`);
258 hookResults.push(`export type ${lazyOperationName}HookResult = ReturnType<typeof use${lazyOperationName}>;`);
259 const suspenseOperationName = this.convertName(nodeName, {
260 suffix: pascalCase('SuspenseQuery'),
261 useTypesPrefix: false,
262 }) + this.config.hooksSuffix;
263 hookFns.push(`export function use${suspenseOperationName}(baseOptions?: ${this.getApolloReactHooksIdentifier()}.SuspenseQueryHookOptions<${operationResultType}, ${operationVariablesTypes}>) {
264 const options = {...defaultOptions, ...baseOptions}
265 return ${this.getApolloReactHooksIdentifier()}.useSuspenseQuery<${operationResultType}, ${operationVariablesTypes}>(${this.getDocumentNodeVariable(node, documentVariableName)}, options);
266 }`);
267 hookResults.push(`export type ${suspenseOperationName}HookResult = ReturnType<typeof use${suspenseOperationName}>;`);
268 }
269 return [...hookFns, ...hookResults].join('\n');
270 }
271 _getHookSuffix(name, operationType) {
272 if (this.config.omitOperationSuffix) {
273 return '';
274 }
275 if (!this.config.dedupeOperationSuffix) {
276 return pascalCase(operationType);
277 }
278 if (name.includes('Query') || name.includes('Mutation') || name.includes('Subscription')) {
279 return '';
280 }
281 return pascalCase(operationType);
282 }
283 _buildResultType(node, operationType, operationResultType, operationVariablesTypes) {
284 var _a, _b;
285 const componentResultType = this.convertName((_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '', {
286 suffix: `${operationType}Result`,
287 useTypesPrefix: false,
288 });
289 switch (node.operation) {
290 case 'query':
291 this.imports.add(this.getApolloReactCommonImport(true));
292 return `export type ${componentResultType} = ${this.getApolloReactCommonIdentifier()}.QueryResult<${operationResultType}, ${operationVariablesTypes}>;`;
293 case 'mutation':
294 this.imports.add(this.getApolloReactCommonImport(true));
295 return `export type ${componentResultType} = ${this.getApolloReactCommonIdentifier()}.MutationResult<${operationResultType}>;`;
296 case 'subscription':
297 this.imports.add(this.getApolloReactCommonImport(true));
298 return `export type ${componentResultType} = ${this.getApolloReactCommonIdentifier()}.SubscriptionResult<${operationResultType}>;`;
299 default:
300 return '';
301 }
302 }
303 _buildWithMutationOptionsType(node, operationResultType, operationVariablesTypes) {
304 var _a, _b;
305 if (node.operation !== 'mutation') {
306 return '';
307 }
308 this.imports.add(this.getApolloReactCommonImport(true));
309 const mutationOptionsType = this.convertName((_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '', {
310 suffix: 'MutationOptions',
311 useTypesPrefix: false,
312 });
313 return `export type ${mutationOptionsType} = ${this.getApolloReactCommonIdentifier()}.BaseMutationOptions<${operationResultType}, ${operationVariablesTypes}>;`;
314 }
315 _buildRefetchFn(node, documentVariableName, operationType, operationVariablesTypes) {
316 var _a, _b;
317 if (node.operation !== 'query') {
318 return '';
319 }
320 const nodeName = (_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
321 const operationName = this.convertName(nodeName, {
322 suffix: this._getHookSuffix(nodeName, operationType),
323 useTypesPrefix: false,
324 }) + this.config.hooksSuffix;
325 const optional = hasRequiredVariables(node) ? '' : '?';
326 return `export function refetch${operationName}(variables${optional}: ${operationVariablesTypes}) {
327 return { query: ${this.getDocumentNodeVariable(node, documentVariableName)}, variables: variables }
328 }`;
329 }
330 buildOperation(node, documentVariableName, operationType, operationResultType, operationVariablesTypes, hasRequiredVariables) {
331 operationResultType = this._externalImportPrefix + operationResultType;
332 operationVariablesTypes = this._externalImportPrefix + operationVariablesTypes;
333 const mutationFn = this.config.withMutationFn || this.config.withComponent
334 ? this._buildMutationFn(node, operationResultType, operationVariablesTypes)
335 : null;
336 const component = this.config.withComponent
337 ? this._buildComponent(node, documentVariableName, operationType, operationResultType, operationVariablesTypes)
338 : null;
339 const hoc = this.config.withHOC
340 ? this._buildOperationHoc(node, documentVariableName, operationResultType, operationVariablesTypes)
341 : null;
342 const hooks = this.config.withHooks
343 ? this._buildHooks(node, operationType, documentVariableName, operationResultType, operationVariablesTypes, hasRequiredVariables)
344 : null;
345 const resultType = this.config.withResultType
346 ? this._buildResultType(node, operationType, operationResultType, operationVariablesTypes)
347 : null;
348 const mutationOptionsType = this.config.withMutationOptionsType
349 ? this._buildWithMutationOptionsType(node, operationResultType, operationVariablesTypes)
350 : null;
351 const refetchFn = this.config.withRefetchFn
352 ? this._buildRefetchFn(node, documentVariableName, operationType, operationVariablesTypes)
353 : null;
354 return [mutationFn, component, hoc, hooks, resultType, mutationOptionsType, refetchFn]
355 .filter(a => a)
356 .join('\n');
357 }
358 get fragments() {
359 var _a, _b;
360 const fragments = super.fragments;
361 if (this._fragments.length === 0 || !this.config.withFragmentHooks) {
362 return fragments;
363 }
364 const operationType = 'Fragment';
365 const hookFns = [fragments];
366 for (const fragment of this._fragments.values()) {
367 if (fragment.isExternal) {
368 continue;
369 }
370 const nodeName = (_a = fragment.name) !== null && _a !== void 0 ? _a : '';
371 const suffix = this._getHookSuffix(nodeName, operationType);
372 const fragmentName = this.convertName(nodeName, {
373 suffix,
374 useTypesPrefix: false,
375 useTypesSuffix: false,
376 }) + this.config.hooksSuffix;
377 const operationTypeSuffix = this.getOperationSuffix(fragmentName, operationType);
378 const operationResultType = this.convertName(nodeName, {
379 suffix: operationTypeSuffix + this._parsedConfig.operationResultSuffix,
380 });
381 const IDType = (_b = this.scalars.ID) !== null && _b !== void 0 ? _b : 'string';
382 const hook = `export function use${fragmentName}<F = { id: ${IDType} }>(identifiers: F) {
383 return ${this.getApolloReactHooksIdentifier()}.use${operationType}<${operationResultType}>({
384 fragment: ${nodeName}${this.config.fragmentVariableSuffix},
385 fragmentName: "${nodeName}",
386 from: {
387 __typename: "${fragment.onType}",
388 ...identifiers,
389 },
390 });
391}`;
392 const hookResults = [
393 `export type ${fragmentName}HookResult = ReturnType<typeof use${fragmentName}>;`,
394 ];
395 hookFns.push([hook, hookResults].join('\n'));
396 }
397 return hookFns.join('\n');
398 }
399}