diff --git a/loader.js b/loader.js index c00a045d..f4b6f70d 100644 --- a/loader.js +++ b/loader.js @@ -7,13 +7,29 @@ const gql = require('./'); // the imported definitions. function expandImports(source, doc) { const lines = source.split('\n'); - let outputCode = ""; + let outputCode = ` + var names = {}; + function unique(defs) { + return defs.filter( + function(def) { + if (def.kind !== 'FragmentDefinition') return true; + var name = def.name.value + if (names[name]) { + return false; + } else { + names[name] = true; + return true; + } + } + ) + } + `; lines.some((line) => { if (line[0] === '#' && line.slice(1).split(' ')[0] === 'import') { const importFile = line.slice(1).split(' ')[1]; const parseDocument = `require(${importFile})`; - const appendDef = `doc.definitions = doc.definitions.concat(${parseDocument}.definitions);`; + const appendDef = `doc.definitions = doc.definitions.concat(unique(${parseDocument}.definitions));`; outputCode += appendDef + "\n"; } return (line.length !== 0 && line[0] !== '#'); diff --git a/test.js b/test.js index ceddadc7..ce5877de 100644 --- a/test.js +++ b/test.js @@ -340,6 +340,53 @@ const assert = require('chai').assert; // document is not the same, but the set of definitions should be. assert.deepEqual(query1.definitions, query2.definitions); }); + + it('ignores duplicate fragments from second-level imports when using the webpack loader', () => { + // take a require function and a query string, use the webpack loader to process it + const load = (require, query) => { + const jsSource = loader.call({ cacheable() {} }, query); + const module = { exports: undefined }; + eval(jsSource); + return module.exports; + } + + const test_require = (path) => { + switch (path) { + case './friends.graphql': + return load(test_require, [ + '#import "./person.graphql"', + 'fragment friends on Hero { friends { ...person } }', + ].join('\n')); + case './enemies.graphql': + return load(test_require, [ + '#import "./person.graphql"', + 'fragment enemies on Hero { enemies { ...person } }', + ].join('\n')); + case './person.graphql': + return load(test_require, 'fragment person on Person { name }\n'); + default: + return null; + }; + }; + + const result = load(test_require, [ + '#import "./friends.graphql"', + '#import "./enemies.graphql"', + 'query { hero { ...friends ...enemies } }', + ].join('\n')); + + assert.equal(result.kind, 'Document'); + assert.equal(result.definitions.length, 4, 'after deduplication, only 4 fragments should remain'); + assert.equal(result.definitions[0].kind, 'OperationDefinition'); + + // the rest of the definitions should be fragments and contain one of + // each: "friends", "enemies", "person". Order does not matter + const fragments = result.definitions.slice(1) + assert(fragments.every(fragment => fragment.kind === 'FragmentDefinition')) + assert(fragments.some(fragment => fragment.name.value === 'friends')) + assert(fragments.some(fragment => fragment.name.value === 'enemies')) + assert(fragments.some(fragment => fragment.name.value === 'person')) + }); }); // How to make this work?