From 297d6e30bbf203c4facc6ecf4e3591b0894c87f6 Mon Sep 17 00:00:00 2001
From: arianon <georgesvalera1997@gmail.com>
Date: Tue, 14 Aug 2018 12:11:17 -0400
Subject: [PATCH] Import GraphQL files from other GraphQL files (closes #1477)
 (#1892)

An alternative to #1817 that doesn't rely on using `graphql-tag/loader`, which is a Webpack loader.

#### What this does:
1. Recursively traverse the GraphQL files.
2. Collect all the imports into a map.
3. Concatenate them.
4. Parse the result into an AST.

#### What this doesn't do:

This PR (as of time of writing) does not have feature parity with `graphql-tag/loader`, meaning that:
* It does not de-duplicate fragments that have the same name. _This is a behavior that I personally disapprove of since it can lead to surprising results instead of throwing an error if a user accidentally (however unlikely) reuses an already defined fragment name._
* It does not collect multiple defined queries/mutations and make them individually require-able from a JavaScript parent.


If the above behaviours are desired then I can implement them.
---
 .../core/parcel/src/assets/GraphqlAsset.js    | 49 ++++++++++++++++++-
 packages/core/parcel/test/graphql.js          | 39 +++++++++++++++
 .../graphql-import/another.graphql            |  4 ++
 .../test/integration/graphql-import/index.js  |  5 ++
 .../integration/graphql-import/local.graphql  |  8 +++
 .../integration/graphql-import/other.graphql  |  6 +++
 6 files changed, 110 insertions(+), 1 deletion(-)
 create mode 100644 packages/core/parcel/test/integration/graphql-import/another.graphql
 create mode 100644 packages/core/parcel/test/integration/graphql-import/index.js
 create mode 100644 packages/core/parcel/test/integration/graphql-import/local.graphql
 create mode 100644 packages/core/parcel/test/integration/graphql-import/other.graphql

diff --git a/packages/core/parcel/src/assets/GraphqlAsset.js b/packages/core/parcel/src/assets/GraphqlAsset.js
index 62e7a3563d95..8517e2574f3a 100644
--- a/packages/core/parcel/src/assets/GraphqlAsset.js
+++ b/packages/core/parcel/src/assets/GraphqlAsset.js
@@ -1,15 +1,62 @@
 const Asset = require('../Asset');
 const localRequire = require('../utils/localRequire');
+const Resolver = require('../Resolver');
+const fs = require('../utils/fs');
+const os = require('os');
+
+const IMPORT_RE = /^# *import +['"](.*)['"] *;? *$/;
 
 class GraphqlAsset extends Asset {
   constructor(name, options) {
     super(name, options);
     this.type = 'js';
+
+    this.gqlMap = new Map();
+    this.gqlResolver = new Resolver(
+      Object.assign({}, this.options, {
+        extensions: ['.gql', '.graphql']
+      })
+    );
+  }
+
+  async traverseImports(name, code) {
+    this.gqlMap.set(name, code);
+
+    await Promise.all(
+      code
+        .split(/\r\n?|\n/)
+        .map(line => line.match(IMPORT_RE))
+        .filter(match => !!match)
+        .map(async ([, importName]) => {
+          let {path: resolved} = await this.gqlResolver.resolve(
+            importName,
+            name
+          );
+
+          if (this.gqlMap.has(resolved)) {
+            return;
+          }
+
+          let code = await fs.readFile(resolved, 'utf8');
+          await this.traverseImports(resolved, code);
+        })
+    );
+  }
+
+  collectDependencies() {
+    for (let [path] of this.gqlMap) {
+      this.addDependency(path, {includedInParent: true});
+    }
   }
 
   async parse(code) {
     let gql = await localRequire('graphql-tag', this.name);
-    return gql(code);
+
+    await this.traverseImports(this.name, code);
+
+    const allCodes = [...this.gqlMap.values()].join(os.EOL);
+
+    return gql(allCodes);
   }
 
   generate() {
diff --git a/packages/core/parcel/test/graphql.js b/packages/core/parcel/test/graphql.js
index ed6eacc93afd..136eb030d709 100644
--- a/packages/core/parcel/test/graphql.js
+++ b/packages/core/parcel/test/graphql.js
@@ -35,4 +35,43 @@ describe('graphql', function() {
       `.definitions
     );
   });
+
+  it('should support importing other graphql files from a graphql file', async function() {
+    let b = await bundle(__dirname + '/integration/graphql-import/index.js');
+
+    await assertBundleTree(b, {
+      name: 'index.js',
+      assets: ['index.js', 'local.graphql'],
+      childBundles: [
+        {
+          type: 'map'
+        }
+      ]
+    });
+
+    let output = await run(b);
+    assert.equal(typeof output, 'function');
+    assert.deepEqual(
+      output().definitions,
+      gql`
+        {
+          user(id: 6) {
+            ...UserFragment
+            ...AnotherUserFragment
+          }
+        }
+
+        fragment UserFragment on User {
+          firstName
+          lastName
+        }
+
+        fragment AnotherUserFragment on User {
+          address
+          email
+        }
+
+      `.definitions
+    );
+  });
 });
diff --git a/packages/core/parcel/test/integration/graphql-import/another.graphql b/packages/core/parcel/test/integration/graphql-import/another.graphql
new file mode 100644
index 000000000000..53656ef91d40
--- /dev/null
+++ b/packages/core/parcel/test/integration/graphql-import/another.graphql
@@ -0,0 +1,4 @@
+fragment AnotherUserFragment on User {
+    address
+    email
+}
\ No newline at end of file
diff --git a/packages/core/parcel/test/integration/graphql-import/index.js b/packages/core/parcel/test/integration/graphql-import/index.js
new file mode 100644
index 000000000000..7e39597facbb
--- /dev/null
+++ b/packages/core/parcel/test/integration/graphql-import/index.js
@@ -0,0 +1,5 @@
+var local = require('./local.graphql');
+
+module.exports = function () {
+  return local;
+};
\ No newline at end of file
diff --git a/packages/core/parcel/test/integration/graphql-import/local.graphql b/packages/core/parcel/test/integration/graphql-import/local.graphql
new file mode 100644
index 000000000000..63630612f5bb
--- /dev/null
+++ b/packages/core/parcel/test/integration/graphql-import/local.graphql
@@ -0,0 +1,8 @@
+# import './other.graphql'
+
+{
+  user(id: 6) {
+    ...UserFragment
+    ...AnotherUserFragment
+  }
+}
\ No newline at end of file
diff --git a/packages/core/parcel/test/integration/graphql-import/other.graphql b/packages/core/parcel/test/integration/graphql-import/other.graphql
new file mode 100644
index 000000000000..aea4d154a6c0
--- /dev/null
+++ b/packages/core/parcel/test/integration/graphql-import/other.graphql
@@ -0,0 +1,6 @@
+# import './another.graphql'
+
+fragment UserFragment on User {
+    firstName
+    lastName
+}
\ No newline at end of file