UNPKG

18.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.ClientSideBaseVisitor = exports.DocumentMode = void 0;
4const tslib_1 = require("tslib");
5const base_visitor_js_1 = require("./base-visitor.js");
6const auto_bind_1 = tslib_1.__importDefault(require("auto-bind"));
7const graphql_1 = require("graphql");
8const dependency_graph_1 = require("dependency-graph");
9const graphql_tag_1 = tslib_1.__importDefault(require("graphql-tag"));
10const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers");
11const utils_js_1 = require("./utils.js");
12const path_1 = require("path");
13const change_case_all_1 = require("change-case-all");
14const imports_js_1 = require("./imports.js");
15const optimize_1 = require("@graphql-tools/optimize");
16graphql_tag_1.default.enableExperimentalFragmentVariables();
17var DocumentMode;
18(function (DocumentMode) {
19 DocumentMode["graphQLTag"] = "graphQLTag";
20 DocumentMode["documentNode"] = "documentNode";
21 DocumentMode["documentNodeImportFragments"] = "documentNodeImportFragments";
22 DocumentMode["external"] = "external";
23 DocumentMode["string"] = "string";
24})(DocumentMode = exports.DocumentMode || (exports.DocumentMode = {}));
25const EXTENSIONS_TO_REMOVE = ['.ts', '.tsx', '.js', '.jsx'];
26class ClientSideBaseVisitor extends base_visitor_js_1.BaseVisitor {
27 constructor(_schema, _fragments, rawConfig, additionalConfig, documents) {
28 super(rawConfig, {
29 scalars: (0, utils_js_1.buildScalarsFromConfig)(_schema, rawConfig),
30 dedupeOperationSuffix: (0, utils_js_1.getConfigValue)(rawConfig.dedupeOperationSuffix, false),
31 optimizeDocumentNode: (0, utils_js_1.getConfigValue)(rawConfig.optimizeDocumentNode, true),
32 omitOperationSuffix: (0, utils_js_1.getConfigValue)(rawConfig.omitOperationSuffix, false),
33 gqlImport: rawConfig.gqlImport || null,
34 documentNodeImport: rawConfig.documentNodeImport || null,
35 noExport: !!rawConfig.noExport,
36 importOperationTypesFrom: (0, utils_js_1.getConfigValue)(rawConfig.importOperationTypesFrom, null),
37 operationResultSuffix: (0, utils_js_1.getConfigValue)(rawConfig.operationResultSuffix, ''),
38 documentVariablePrefix: (0, utils_js_1.getConfigValue)(rawConfig.documentVariablePrefix, ''),
39 documentVariableSuffix: (0, utils_js_1.getConfigValue)(rawConfig.documentVariableSuffix, 'Document'),
40 fragmentVariablePrefix: (0, utils_js_1.getConfigValue)(rawConfig.fragmentVariablePrefix, ''),
41 fragmentVariableSuffix: (0, utils_js_1.getConfigValue)(rawConfig.fragmentVariableSuffix, 'FragmentDoc'),
42 documentMode: ((rawConfig) => {
43 if (typeof rawConfig.noGraphQLTag === 'boolean') {
44 return rawConfig.noGraphQLTag ? DocumentMode.documentNode : DocumentMode.graphQLTag;
45 }
46 return (0, utils_js_1.getConfigValue)(rawConfig.documentMode, DocumentMode.graphQLTag);
47 })(rawConfig),
48 importDocumentNodeExternallyFrom: (0, utils_js_1.getConfigValue)(rawConfig.importDocumentNodeExternallyFrom, ''),
49 pureMagicComment: (0, utils_js_1.getConfigValue)(rawConfig.pureMagicComment, false),
50 experimentalFragmentVariables: (0, utils_js_1.getConfigValue)(rawConfig.experimentalFragmentVariables, false),
51 ...additionalConfig,
52 });
53 this._schema = _schema;
54 this._fragments = _fragments;
55 this._collectedOperations = [];
56 this._documents = [];
57 this._additionalImports = [];
58 this._imports = new Set();
59 this._documents = documents;
60 (0, auto_bind_1.default)(this);
61 }
62 _extractFragments(document, withNested = false) {
63 if (!document) {
64 return [];
65 }
66 const names = new Set();
67 (0, plugin_helpers_1.oldVisit)(document, {
68 enter: {
69 FragmentSpread: (node) => {
70 names.add(node.name.value);
71 if (withNested) {
72 const foundFragment = this._fragments.find(f => f.name === node.name.value);
73 if (foundFragment) {
74 const childItems = this._extractFragments(foundFragment.node, true);
75 if (childItems && childItems.length > 0) {
76 for (const item of childItems) {
77 names.add(item);
78 }
79 }
80 }
81 }
82 },
83 },
84 });
85 return Array.from(names);
86 }
87 _transformFragments(document) {
88 const includeNestedFragments = this.config.documentMode === DocumentMode.documentNode ||
89 (this.config.dedupeFragments && document.kind === 'OperationDefinition');
90 return this._extractFragments(document, includeNestedFragments).map(document => this.getFragmentVariableName(document));
91 }
92 _includeFragments(fragments, nodeKind) {
93 if (fragments && fragments.length > 0) {
94 if (this.config.documentMode === DocumentMode.documentNode) {
95 return this._fragments
96 .filter(f => fragments.includes(this.getFragmentVariableName(f.name)))
97 .map(fragment => (0, graphql_1.print)(fragment.node))
98 .join('\n');
99 }
100 if (this.config.documentMode === DocumentMode.documentNodeImportFragments) {
101 return '';
102 }
103 if (this.config.dedupeFragments && nodeKind !== 'OperationDefinition') {
104 return '';
105 }
106 return `${fragments.map(name => '${' + name + '}').join('\n')}`;
107 }
108 return '';
109 }
110 _prepareDocument(documentStr) {
111 return documentStr;
112 }
113 _gql(node) {
114 const fragments = this._transformFragments(node);
115 const doc = this._prepareDocument(`
116 ${(0, graphql_1.print)(node).split('\\').join('\\\\') /* Re-escape escaped values in GraphQL syntax */}
117 ${this._includeFragments(fragments, node.kind)}`);
118 if (this.config.documentMode === DocumentMode.documentNode) {
119 let gqlObj = (0, graphql_tag_1.default)([doc]);
120 if (this.config.optimizeDocumentNode) {
121 gqlObj = (0, optimize_1.optimizeDocumentNode)(gqlObj);
122 }
123 return JSON.stringify(gqlObj);
124 }
125 if (this.config.documentMode === DocumentMode.documentNodeImportFragments) {
126 let gqlObj = (0, graphql_tag_1.default)([doc]);
127 if (this.config.optimizeDocumentNode) {
128 gqlObj = (0, optimize_1.optimizeDocumentNode)(gqlObj);
129 }
130 if (fragments.length > 0 && (!this.config.dedupeFragments || node.kind === 'OperationDefinition')) {
131 const definitions = [
132 ...gqlObj.definitions.map(t => JSON.stringify(t)),
133 ...fragments.map(name => `...${name}.definitions`),
134 ].join();
135 return `{"kind":"${graphql_1.Kind.DOCUMENT}","definitions":[${definitions}]}`;
136 }
137 return JSON.stringify(gqlObj);
138 }
139 if (this.config.documentMode === DocumentMode.string) {
140 return '`' + doc + '`';
141 }
142 const gqlImport = this._parseImport(this.config.gqlImport || 'graphql-tag');
143 return (gqlImport.propName || 'gql') + '`' + doc + '`';
144 }
145 _generateFragment(fragmentDocument) {
146 const name = this.getFragmentVariableName(fragmentDocument);
147 const fragmentTypeSuffix = this.getFragmentSuffix(fragmentDocument);
148 return `export const ${name} =${this.config.pureMagicComment ? ' /*#__PURE__*/' : ''} ${this._gql(fragmentDocument)}${this.getDocumentNodeSignature(this.convertName(fragmentDocument.name.value, {
149 useTypesPrefix: true,
150 suffix: fragmentTypeSuffix,
151 }), this.config.experimentalFragmentVariables
152 ? this.convertName(fragmentDocument.name.value, {
153 suffix: fragmentTypeSuffix + 'Variables',
154 })
155 : 'unknown', fragmentDocument)};`;
156 }
157 get fragmentsGraph() {
158 const graph = new dependency_graph_1.DepGraph({ circular: true });
159 for (const fragment of this._fragments) {
160 if (graph.hasNode(fragment.name)) {
161 const cachedAsString = (0, graphql_1.print)(graph.getNodeData(fragment.name).node);
162 const asString = (0, graphql_1.print)(fragment.node);
163 if (cachedAsString !== asString) {
164 throw new Error(`Duplicated fragment called '${fragment.name}'!`);
165 }
166 }
167 graph.addNode(fragment.name, fragment);
168 }
169 this._fragments.forEach(fragment => {
170 const depends = this._extractFragments(fragment.node);
171 if (depends && depends.length > 0) {
172 depends.forEach(name => {
173 graph.addDependency(fragment.name, name);
174 });
175 }
176 });
177 return graph;
178 }
179 get fragments() {
180 if (this._fragments.length === 0 || this.config.documentMode === DocumentMode.external) {
181 return '';
182 }
183 const graph = this.fragmentsGraph;
184 const orderedDeps = graph.overallOrder();
185 const localFragments = orderedDeps
186 .filter(name => !graph.getNodeData(name).isExternal)
187 .map(name => this._generateFragment(graph.getNodeData(name).node));
188 return localFragments.join('\n');
189 }
190 _parseImport(importStr) {
191 // This is a special case when we want to ignore importing, and just use `gql` provided from somewhere else
192 // Plugins that uses that will need to ensure to add import/declaration for the gql identifier
193 if (importStr === 'gql') {
194 return {
195 moduleName: null,
196 propName: 'gql',
197 };
198 }
199 // This is a special use case, when we don't want this plugin to manage the import statement
200 // of the gql tag. In this case, we provide something like `Namespace.gql` and it will be used instead.
201 if (importStr.includes('.gql')) {
202 return {
203 moduleName: null,
204 propName: importStr,
205 };
206 }
207 const [moduleName, propName] = importStr.split('#');
208 return {
209 moduleName,
210 propName,
211 };
212 }
213 _generateImport({ moduleName, propName }, varName, isTypeImport) {
214 const typeImport = isTypeImport && this.config.useTypeImports ? 'import type' : 'import';
215 const propAlias = propName === varName ? '' : ` as ${varName}`;
216 if (moduleName) {
217 return `${typeImport} ${propName ? `{ ${propName}${propAlias} }` : varName} from '${moduleName}';`;
218 }
219 return null;
220 }
221 clearExtension(path) {
222 const extension = (0, path_1.extname)(path);
223 if (EXTENSIONS_TO_REMOVE.includes(extension)) {
224 return path.replace(/\.[^/.]+$/, '');
225 }
226 return path;
227 }
228 getImports(options = {}) {
229 (this._additionalImports || []).forEach(i => this._imports.add(i));
230 switch (this.config.documentMode) {
231 case DocumentMode.documentNode:
232 case DocumentMode.documentNodeImportFragments: {
233 const documentNodeImport = this._parseImport(this.config.documentNodeImport || 'graphql#DocumentNode');
234 const tagImport = this._generateImport(documentNodeImport, 'DocumentNode', true);
235 if (tagImport) {
236 this._imports.add(tagImport);
237 }
238 break;
239 }
240 case DocumentMode.graphQLTag: {
241 const gqlImport = this._parseImport(this.config.gqlImport || 'graphql-tag');
242 const tagImport = this._generateImport(gqlImport, 'gql', false);
243 if (tagImport) {
244 this._imports.add(tagImport);
245 }
246 break;
247 }
248 case DocumentMode.external: {
249 if (this._collectedOperations.length > 0) {
250 if (this.config.importDocumentNodeExternallyFrom === 'near-operation-file' && this._documents.length === 1) {
251 let documentPath = `./${this.clearExtension((0, path_1.basename)(this._documents[0].location))}`;
252 if (this.config.emitLegacyCommonJSImports) {
253 documentPath += '.js';
254 }
255 this._imports.add(`import * as Operations from '${documentPath}';`);
256 }
257 else {
258 if (!this.config.importDocumentNodeExternallyFrom) {
259 // eslint-disable-next-line no-console
260 console.warn('importDocumentNodeExternallyFrom must be provided if documentMode=external');
261 }
262 this._imports.add(`import * as Operations from '${this.clearExtension(this.config.importDocumentNodeExternallyFrom)}';`);
263 }
264 }
265 break;
266 }
267 default:
268 break;
269 }
270 if (!options.excludeFragments && !this.config.globalNamespace) {
271 const { documentMode, fragmentImports } = this.config;
272 if (documentMode === DocumentMode.graphQLTag ||
273 documentMode === DocumentMode.string ||
274 documentMode === DocumentMode.documentNodeImportFragments) {
275 // keep track of what imports we've already generated so we don't try
276 // to import the same identifier twice
277 const alreadyImported = new Map();
278 const deduplicatedImports = fragmentImports
279 .map(fragmentImport => {
280 const { path, identifiers } = fragmentImport.importSource;
281 if (!alreadyImported.has(path)) {
282 alreadyImported.set(path, new Set());
283 }
284 const alreadyImportedForPath = alreadyImported.get(path);
285 const newIdentifiers = identifiers.filter(identifier => !alreadyImportedForPath.has(identifier.name));
286 newIdentifiers.forEach(newIdentifier => alreadyImportedForPath.add(newIdentifier.name));
287 // filter the set of identifiers in this fragment import to only
288 // the ones we haven't already imported from this path
289 return {
290 ...fragmentImport,
291 importSource: {
292 ...fragmentImport.importSource,
293 identifiers: newIdentifiers,
294 },
295 emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
296 };
297 })
298 // remove any imports that now have no identifiers in them
299 .filter(fragmentImport => fragmentImport.importSource.identifiers.length > 0);
300 deduplicatedImports.forEach(fragmentImport => {
301 if (fragmentImport.outputPath !== fragmentImport.importSource.path) {
302 this._imports.add((0, imports_js_1.generateFragmentImportStatement)(fragmentImport, 'document'));
303 }
304 });
305 }
306 }
307 return Array.from(this._imports);
308 }
309 buildOperation(_node, _documentVariableName, _operationType, _operationResultType, _operationVariablesTypes, _hasRequiredVariables) {
310 return null;
311 }
312 getDocumentNodeSignature(_resultType, _variablesTypes, _node) {
313 if (this.config.documentMode === DocumentMode.documentNode ||
314 this.config.documentMode === DocumentMode.documentNodeImportFragments) {
315 return ` as unknown as DocumentNode`;
316 }
317 return '';
318 }
319 /**
320 * Checks if the specific operation has variables that are non-null (required), and also doesn't have default.
321 * This is useful for deciding of `variables` should be optional or not.
322 * @param node
323 */
324 checkVariablesRequirements(node) {
325 const variables = node.variableDefinitions || [];
326 if (variables.length === 0) {
327 return false;
328 }
329 return variables.some(variableDef => variableDef.type.kind === graphql_1.Kind.NON_NULL_TYPE && !variableDef.defaultValue);
330 }
331 getOperationVariableName(node) {
332 return this.convertName(node, {
333 suffix: this.config.documentVariableSuffix,
334 prefix: this.config.documentVariablePrefix,
335 useTypesPrefix: false,
336 });
337 }
338 OperationDefinition(node) {
339 this._collectedOperations.push(node);
340 const documentVariableName = this.getOperationVariableName(node);
341 const operationType = (0, change_case_all_1.pascalCase)(node.operation);
342 const operationTypeSuffix = this.getOperationSuffix(node, operationType);
343 const operationResultType = this.convertName(node, {
344 suffix: operationTypeSuffix + this._parsedConfig.operationResultSuffix,
345 });
346 const operationVariablesTypes = this.convertName(node, {
347 suffix: operationTypeSuffix + 'Variables',
348 });
349 let documentString = '';
350 if (this.config.documentMode !== DocumentMode.external &&
351 documentVariableName !== '' // only generate exports for named queries
352 ) {
353 documentString = `${this.config.noExport ? '' : 'export'} const ${documentVariableName} =${this.config.pureMagicComment ? ' /*#__PURE__*/' : ''} ${this._gql(node)}${this.getDocumentNodeSignature(operationResultType, operationVariablesTypes, node)};`;
354 }
355 const hasRequiredVariables = this.checkVariablesRequirements(node);
356 const additional = this.buildOperation(node, documentVariableName, operationType, operationResultType, operationVariablesTypes, hasRequiredVariables);
357 return [documentString, additional].filter(a => a).join('\n');
358 }
359}
360exports.ClientSideBaseVisitor = ClientSideBaseVisitor;