UNPKG

5.41 kBJavaScriptView Raw
1"use strict";
2
3const os = require('os');
4const gql = require('./main.js');
5
6// Takes `source` (the source GraphQL query string)
7// and `doc` (the parsed GraphQL document) and tacks on
8// the imported definitions.
9function expandImports(source, doc) {
10 const lines = source.split(/\r\n|\r|\n/);
11 let outputCode = `
12 var names = {};
13 function unique(defs) {
14 return defs.filter(
15 function(def) {
16 if (def.kind !== 'FragmentDefinition') return true;
17 var name = def.name.value
18 if (names[name]) {
19 return false;
20 } else {
21 names[name] = true;
22 return true;
23 }
24 }
25 )
26 }
27 `;
28
29 lines.some((line) => {
30 if (line[0] === '#' && line.slice(1).split(' ')[0] === 'import') {
31 const importFile = line.slice(1).split(' ')[1];
32 const parseDocument = `require(${importFile})`;
33 const appendDef = `doc.definitions = doc.definitions.concat(unique(${parseDocument}.definitions));`;
34 outputCode += appendDef + os.EOL;
35 }
36 return (line.length !== 0 && line[0] !== '#');
37 });
38
39 return outputCode;
40}
41
42module.exports = function(source) {
43 this.cacheable();
44 const doc = gql`${source}`;
45 let headerCode = `
46 var doc = ${JSON.stringify(doc)};
47 doc.loc.source = ${JSON.stringify(doc.loc.source)};
48 `;
49
50 let outputCode = "";
51
52 // Allow multiple query/mutation definitions in a file. This parses out dependencies
53 // at compile time, and then uses those at load time to create minimal query documents
54 // We cannot do the latter at compile time due to how the #import code works.
55 let operationCount = doc.definitions.reduce(function(accum, op) {
56 if (op.kind === "OperationDefinition" || op.kind === "FragmentDefinition") {
57 return accum + 1;
58 }
59
60 return accum;
61 }, 0);
62
63 if (operationCount < 1) {
64 outputCode += `
65 module.exports = doc;
66 `
67 } else {
68 outputCode += `
69 // Collect any fragment/type references from a node, adding them to the refs Set
70 function collectFragmentReferences(node, refs) {
71 if (node.kind === "FragmentSpread") {
72 refs.add(node.name.value);
73 } else if (node.kind === "VariableDefinition") {
74 var type = node.type;
75 if (type.kind === "NamedType") {
76 refs.add(type.name.value);
77 }
78 }
79
80 if (node.selectionSet) {
81 node.selectionSet.selections.forEach(function(selection) {
82 collectFragmentReferences(selection, refs);
83 });
84 }
85
86 if (node.variableDefinitions) {
87 node.variableDefinitions.forEach(function(def) {
88 collectFragmentReferences(def, refs);
89 });
90 }
91
92 if (node.definitions) {
93 node.definitions.forEach(function(def) {
94 collectFragmentReferences(def, refs);
95 });
96 }
97 }
98
99 var definitionRefs = {};
100 (function extractReferences() {
101 doc.definitions.forEach(function(def) {
102 if (def.name) {
103 var refs = new Set();
104 collectFragmentReferences(def, refs);
105 definitionRefs[def.name.value] = refs;
106 }
107 });
108 })();
109
110 function findOperation(doc, name) {
111 for (var i = 0; i < doc.definitions.length; i++) {
112 var element = doc.definitions[i];
113 if (element.name && element.name.value == name) {
114 return element;
115 }
116 }
117 }
118
119 function oneQuery(doc, operationName) {
120 // Copy the DocumentNode, but clear out the definitions
121 var newDoc = {
122 kind: doc.kind,
123 definitions: [findOperation(doc, operationName)]
124 };
125 if (doc.hasOwnProperty("loc")) {
126 newDoc.loc = doc.loc;
127 }
128
129 // Now, for the operation we're running, find any fragments referenced by
130 // it or the fragments it references
131 var opRefs = definitionRefs[operationName] || new Set();
132 var allRefs = new Set();
133 var newRefs = new Set();
134
135 // IE 11 doesn't support "new Set(iterable)", so we add the members of opRefs to newRefs one by one
136 opRefs.forEach(function(refName) {
137 newRefs.add(refName);
138 });
139
140 while (newRefs.size > 0) {
141 var prevRefs = newRefs;
142 newRefs = new Set();
143
144 prevRefs.forEach(function(refName) {
145 if (!allRefs.has(refName)) {
146 allRefs.add(refName);
147 var childRefs = definitionRefs[refName] || new Set();
148 childRefs.forEach(function(childRef) {
149 newRefs.add(childRef);
150 });
151 }
152 });
153 }
154
155 allRefs.forEach(function(refName) {
156 var op = findOperation(doc, refName);
157 if (op) {
158 newDoc.definitions.push(op);
159 }
160 });
161
162 return newDoc;
163 }
164
165 module.exports = doc;
166 `
167
168 for (const op of doc.definitions) {
169 if (op.kind === "OperationDefinition" || op.kind === "FragmentDefinition") {
170 if (!op.name) {
171 if (operationCount > 1) {
172 throw "Query/mutation names are required for a document with multiple definitions";
173 } else {
174 continue;
175 }
176 }
177
178 const opName = op.name.value;
179 outputCode += `
180 module.exports["${opName}"] = oneQuery(doc, "${opName}");
181 `
182 }
183 }
184 }
185
186 const importOutputCode = expandImports(source, doc);
187 const allCode = headerCode + os.EOL + importOutputCode + os.EOL + outputCode + os.EOL;
188
189 return allCode;
190};