diff --git a/loader.js b/loader.js index e7bbad9c..5c5a10f3 100644 --- a/loader.js +++ b/loader.js @@ -41,10 +41,23 @@ function expandImports(source, doc) { module.exports = function(source) { this.cacheable(); - const doc = gql`${source}`; + const doc = gql(source, true); + + if (doc.definitions) { + for (var i = 0; i < doc.definitions.length; i++) { + if (doc.definitions[i].loc) { + doc.definitions[i].loc = { + start: doc.definitions[i].loc.start, + end: doc.definitions[i].loc.end, + source: doc.definitions[i].loc.source + } + } + } + } + const outputCode = ` - var doc = ${JSON.stringify(doc)}; - doc.loc.source = ${JSON.stringify(doc.loc.source)}; + var doc = ${JSON.stringify(doc, null, 4)}; + doc.loc.source = ${JSON.stringify(doc.loc.source, null, 4)}; `; const importOutputCode = expandImports(source, doc); diff --git a/src/index.js b/src/index.js index e0ba03c0..fd9e2e29 100644 --- a/src/index.js +++ b/src/index.js @@ -128,7 +128,7 @@ function stripLoc(doc, removeLocAtThisLevel) { return doc; } -function parseDocument(doc) { +function parseDocument(doc, asImport) { var cacheKey = normalize(doc); if (docCache[cacheKey]) { @@ -143,7 +143,12 @@ function parseDocument(doc) { // check that all "new" fragments inside the documents are consistent with // existing fragments of the same name parsed = processFragments(parsed); - parsed = stripLoc(parsed, false); + if (asImport) { + parsed.definitions = stripLoc(parsed.definitions, false); + } + else { + parsed = stripLoc(parsed, false); + } docCache[cacheKey] = parsed; return parsed; @@ -152,15 +157,21 @@ function parseDocument(doc) { // XXX This should eventually disallow arbitrary string interpolation, like Relay does function gql(/* arguments */) { var args = Array.prototype.slice.call(arguments); + var asImport = args[args.length - 1] === true; + if (asImport) { + args = args.slice(0, -1); + } var literals = args[0]; // We always get literals[0] and then matching post literals for each arg given var result = (typeof(literals) === "string") ? literals : literals[0]; + var definitions = []; for (var i = 1; i < args.length; i++) { if (args[i] && args[i].kind && args[i].kind === 'Document') { result += args[i].loc.source.body; + definitions = definitions.concat(args[i].definitions) } else { result += args[i]; } @@ -168,7 +179,19 @@ function gql(/* arguments */) { result += literals[i]; } - return parseDocument(result); + definitions = definitions.filter(function (def) { + return def.kind === 'FragmentDefinition' && def.loc + }) + + var doc = parseDocument(result, asImport || definitions.length); + + + if (definitions.length > 0) { + doc.definitions = doc.definitions.concat(definitions) + doc = processFragments(doc) + } + + return doc; } // Support typescript, which isn't as nice as Babel about default exports diff --git a/test.js b/test.js index 08145f9d..d0494daa 100644 --- a/test.js +++ b/test.js @@ -70,6 +70,38 @@ const assert = require('chai').assert; assert.equal(definitions[1].kind, 'FragmentDefinition'); }); + it('correctly interpolates imports of other files through the webpack loader', () => { + const query =`#import "./fragment_definition.graphql" + + fragment BooksAuthor on Book { + author { + ...AuthorDetails + } + } + `; + const jsSource = loader.call({ cacheable() {} }, query); + + const oldRequire = require; + const module = { exports: undefined }; + const require = (path) => { + assert.equal(path, './fragment_definition.graphql'); + return gql(` + fragment AuthorDetails on Author { + firstName + lastName + }`, true); + }; + + eval(jsSource); + + const document = gql`query { ...BooksAuthor } ${module.exports}`; + assert.equal(document.kind, 'Document'); + assert.equal(document.definitions.length, 3); + assert.equal(document.definitions[0].kind, 'OperationDefinition'); + assert.equal(document.definitions[1].kind, 'FragmentDefinition'); + assert.equal(document.definitions[2].kind, 'FragmentDefinition'); + }); + it('does not complain when presented with normal comments', (done) => { assert.doesNotThrow(() => { const query = `#normal comment