UNPKG

@graphql-codegen/near-operation-file-preset

Version:

GraphQL Code Generator preset for generating operation code near the operation file

148 lines (147 loc) • 6.78 kB
import { Kind, print } from 'graphql'; import { BaseVisitor, buildScalarsFromConfig, DocumentMode, getConfigValue, getPossibleTypes, } from '@graphql-codegen/visitor-plugin-common'; import { analyzeFragmentUsage } from './utils.js'; /** * Creates a fragment's type imports based on possible types and usage */ function createFragmentTypeImports(baseVisitor, fragmentName, possibleTypes, usedTypes) { const fragmentSuffix = baseVisitor.getFragmentSuffix(fragmentName); if (possibleTypes.length === 1) { return [ { name: baseVisitor.convertName(fragmentName, { useTypesPrefix: true, suffix: fragmentSuffix, }), kind: 'type', }, ]; } if (possibleTypes.length > 0) { const typesToImport = usedTypes && usedTypes.length > 0 ? usedTypes : possibleTypes; return typesToImport.map(typeName => ({ name: baseVisitor.convertName(fragmentName, { useTypesPrefix: true, suffix: `_${typeName}` + (fragmentSuffix.length > 0 ? `_${fragmentSuffix}` : ''), }), kind: 'type', })); } return []; } /** * Used by `buildFragmentResolver` to build a mapping of fragmentNames to paths, nodes, and other useful info */ function buildFragmentRegistry({ generateFilePath }, { documents }, schemaObject) { const duplicateFragmentNames = []; const registry = documents.reduce((prev, documentRecord) => { const fragments = documentRecord.document.definitions.filter(d => d.kind === Kind.FRAGMENT_DEFINITION); for (const fragment of fragments) { const schemaType = schemaObject.getType(fragment.typeCondition.name.value); if (!schemaType) { throw new Error(`Fragment "${fragment.name.value}" is set on non-existing type "${fragment.typeCondition.name.value}"!`); } const filePath = generateFilePath({ location: documentRecord.location, meta: { operations: [], fragments: [fragment] }, }); const fragmentName = fragment.name.value; const possibleTypes = getPossibleTypes(schemaObject, schemaType); const possibleTypeNames = possibleTypes.map(t => t.name); if (prev[fragmentName] && print(fragment) !== print(prev[fragmentName].node)) { duplicateFragmentNames.push(fragmentName); } prev[fragmentName] = { filePath, onType: fragment.typeCondition.name.value, node: fragment, possibleTypes: possibleTypeNames, }; } return prev; }, {}); if (duplicateFragmentNames.length) { throw new Error(`Multiple fragments with the name(s) "${duplicateFragmentNames.join(', ')}" were found.`); } return registry; } /** * Creates a BaseVisitor with standard configuration */ function createBaseVisitor(config, schemaObject) { return new BaseVisitor(config, { scalars: buildScalarsFromConfig(schemaObject, config), dedupeOperationSuffix: getConfigValue(config.dedupeOperationSuffix, false), omitOperationSuffix: getConfigValue(config.omitOperationSuffix, false), fragmentVariablePrefix: getConfigValue(config.fragmentVariablePrefix, ''), fragmentVariableSuffix: getConfigValue(config.fragmentVariableSuffix, 'FragmentDoc'), }); } /** * Builds a fragment "resolver" that collects `externalFragments` definitions and `fragmentImportStatements` */ export default function buildFragmentResolver(collectorOptions, presetOptions, schemaObject, dedupeFragments = false) { const { config } = presetOptions; const baseVisitor = createBaseVisitor(config, schemaObject); const fragmentRegistry = buildFragmentRegistry(collectorOptions, presetOptions, schemaObject); const { baseOutputDir } = presetOptions; const { baseDir, typesImport } = collectorOptions; function resolveFragments(generatedFilePath, documentFileContent) { var _a; var _b; const { fragmentsInUse, usedFragmentTypes, operationFragments } = analyzeFragmentUsage(documentFileContent, fragmentRegistry, schemaObject); // graphQLTag operations inline every fragment they transitively spread const { documentMode = DocumentMode.graphQLTag } = config; const isGraphQLTagMode = documentMode === DocumentMode.graphQLTag; const externalFragments = []; const fragmentFileImports = {}; for (const [fragmentName, level] of Object.entries(fragmentsInUse)) { const fragmentDetails = fragmentRegistry[fragmentName]; if (!fragmentDetails) continue; // don't emit imports to the same location if (fragmentDetails.filePath !== generatedFilePath) { const imports = []; const isSpreadDirectly = level === 0 || dedupeFragments; const needsDocument = isGraphQLTagMode ? operationFragments.has(fragmentName) : isSpreadDirectly; if (needsDocument) { imports.push({ name: baseVisitor.getFragmentVariableName(fragmentName), kind: 'document', }); } if (isSpreadDirectly) { imports.push(...createFragmentTypeImports(baseVisitor, fragmentName, fragmentDetails.possibleTypes, usedFragmentTypes[fragmentName] || [])); } if (imports.length > 0) { ((_a = fragmentFileImports[_b = fragmentDetails.filePath]) !== null && _a !== void 0 ? _a : (fragmentFileImports[_b] = [])).push(...imports); } } externalFragments.push({ level, isExternal: true, name: fragmentName, onType: fragmentDetails.onType, node: fragmentDetails.node, }); } return { externalFragments, fragmentImports: Object.entries(fragmentFileImports).map(([fragmentsFilePath, identifiers]) => ({ baseDir, baseOutputDir, outputPath: generatedFilePath, importSource: { path: fragmentsFilePath, identifiers, }, emitLegacyCommonJSImports: presetOptions.config.emitLegacyCommonJSImports, importExtension: presetOptions.config.importExtension, typesImport, })), }; } return resolveFragments; }