loader.js 5.3 KB

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