Repository: FormidableLabs/urql
Pull request #1189 (2020-12-03 06:04:53)

(graphcache) - Replace buildClientSchema with lean implementation
## Summary

Our largest impact on `@urql/exchange-graphcache` is our usage of `graphql/utilities/buildClientSchema` which uses `GraphQLSchema` and other heavy implementations, which are basically server-side implementations with a lot of invariants. Given that we know that with `@urql/introspection` and other introspection methods the result can be trusted we don't even need any of the strict validation of `buildClientSchema` and what it pulls in.

Furthmore, we don't need any of the `graphql/type/definition` types, e.g. `GraphQLObjectType` and helpers like `isObjectType` if we transform the client schema to a format that is just good enough for our purposes.

This change increases `@urql/exchange-graphcache`'s size slightly from `6.74kB gzip` to `6.93kB gzip`. However, the impact on what's pulled in from `graphql` is significant and we save in `-9.61kB gzip` (up to; lowest, `8.53kB`). This makes a **net impact** of `-9.42kB gzip`

## Set of changes

- Reimplement `buildClientSchema` and replace `GraphQLSchema` types
- Started to drill down `buildClientSchema` to what's necessary, but then transitioned to just using string-based schema structure
- Update `store.ts` and `schemaPredicates.ts` to use new implementation

No unit tests had to be updated, which is encouraging.
The only remaining usage of `graphql` compared to `@urql/core` is `valueFromASTUntyped`

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: bf96d289da36882388d87f792389b07e2f464586

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/FormidableLabs/urql/new/refactor/build-client-schema?filename=.changeset/thick-emus-travel.md&value=---%0A%22%40urql%2Fexchange-graphcache%22%3A%20patch%0A---%0A%0ARFC%3A%20(graphcache)%20-%20Replace%20GraphQLSchema%20with%20custom%20implementation%0A)



Patch:
diff --git a/.changeset/fair-flies-grab.md b/.changeset/fair-flies-grab.md
new file mode 100644
index 00000000..a5a8fa0d
--- /dev/null
+++ b/.changeset/fair-flies-grab.md
@@ -0,0 +1,9 @@
+---
+'@urql/core': minor
+'@urql/preact': patch
+'urql': patch
+'@urql/svelte': patch
+'@urql/vue': patch
+---
+
+Add a built-in `gql` tag function helper to `@urql/core`. This behaves similarly to `graphql-tag` but only warns about _locally_ duplicated fragment names rather than globally. It also primes `@urql/core`'s key cache with the parsed `DocumentNode`.
diff --git a/.changeset/nervous-beds-battle.md b/.changeset/nervous-beds-battle.md
new file mode 100644
index 00000000..227cf535
--- /dev/null
+++ b/.changeset/nervous-beds-battle.md
@@ -0,0 +1,8 @@
+---
+'@urql/exchange-graphcache': patch
+---
+
+Replace `graphql/utilities/buildClientSchema.mjs` with a custom-tailored, lighter implementation
+built into `@urql/exchange-graphcache`. This will appear to increase its size by about `0.2kB gzip`
+but will actually save around `8.5kB gzip` to `9.4kB gzip` in any production bundle by using less of
+`graphql`'s code.
diff --git a/docs/api/core.md b/docs/api/core.md
index 467bf061..09d7098b 100644
--- a/docs/api/core.md
+++ b/docs/api/core.md
@@ -353,6 +353,36 @@ errorExchange({
 
 ## Utilities
 
+### gql
+
+This is a `gql` tagged template literal function, similar to the one that's also commonly known from
+`graphql-tag`. It can be used to write GraphQL documents in a tagged template literal and returns a
+parsed `DocumentNode` that's primed against the `createRequest`'s cache for `key`s.
+
+```js
+import { gql } from '@urql/core';
+
+const SharedFragment = gql`
+  fragment UserFrag on User {
+    id
+    name
+  }
+`;
+
+gql`
+  query {
+    user
+    ...UserFrag
+  }
+
+  ${SharedFragment}
+`;
+```
+
+Unlike `graphql-tag`, this function outputs a warning in development when names of fragments in the
+document are duplicated. It does not output warnings when fragment names were duplicated globally
+however.
+
 ### stringifyVariables
 
 This function is a variation of `JSON.stringify` that sorts any object's keys that is being
diff --git a/docs/api/graphcache.md b/docs/api/graphcache.md
index 081d91a2..b502c220 100644
--- a/docs/api/graphcache.md
+++ b/docs/api/graphcache.md
@@ -284,7 +284,7 @@ The method will then attempt to read the entity according to the fragment entire
 data. If any data is uncached and missing it'll return `null`.
 
 ```js
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 
 cache.readFragment(
   gql`
@@ -303,7 +303,7 @@ present on the fragment itself.
 If any fields on the fragment require variables, you can pass them as the third argument like so:
 
 ```js
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 
 cache.readFragment(
   gql`
@@ -355,7 +355,7 @@ however the second argument, `data`, should not only contain properties that are
 an entity key from the given data, but also the fields that will be written:
 
 ```js
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 
 cache.writeFragment(
   gql`
diff --git a/docs/basics/queries.md b/docs/basics/queries.md
index 1ffb9617..b791d610 100644
--- a/docs/basics/queries.md
+++ b/docs/basics/queries.md
@@ -815,3 +815,87 @@ There are some more tricks we can use with `useQuery`. [Read more about its API
 it.](../api/vue.md#usequery)
 
 [On the next page we'll learn about "Mutations" rather than Queries.](./mutations.md#vue)
+
+## Core
+
+All framework bindings — meaning `urql`, `@urql/preact`, `@urql/svelte`, and `@urql/vue` — reexport
+all exports of our `@urql/core` core library. This package contains the
+[`Client`](../api/core.md#client), built-in exchanges, and other utilities that are shared between
+all bindings.
+
+### gql
+
+A notable utility function is the `gql` tagged template literal function, which is a drop-in
+replacement for `graphql-tag`, if you're coming from other GraphQL clients.
+
+Wherever `urql` accepts a query document, you may either pass a string or a `DocumentNode`. `gql` is
+a utility that allows a `DocumentNode` to be created directly, and others to be interpolated into
+it, which is useful for fragments for instance. This function will often also mark GraphQL documents
+for syntax highlighting in most code editors.
+
+In most examples we may have passed a string to define a query document, like so:
+
+```js
+const TodosQuery = `
+  query {
+    todos {
+      id
+      title
+    }
+  }
+`;
+```
+
+We may also use the `gql` tag function to create a `DocumentNode` directly:
+
+```js
+import { gql } from '@urql/core';
+
+const TodosQuery = gql`
+  query {
+    todos {
+      id
+      title
+    }
+  }
+`;
+```
+
+Since all framework bindings also re-export `@urql/core`, we may also import `gql` from `'urql'`,
+`'@urql/svelte'` and other bindings directly.
+
+We can also start interpolating other documents into the tag function. This is useful to compose
+fragment documents into a larger query, since it's common to define fragments across components of
+an app to spread out data dependencies. If we accidentally use a duplicate fragment name in a
+document, `gql` will log a warning, since GraphQL APIs won't accept duplicate names.
+
+```js
+import { gql } from '@urql/core';
+
+const TodoFragment = gql`
+  fragment SmallTodo on Todo {
+    id
+    title
+  }
+`;
+
+const TodosQuery = gql`
+  query {
+    todos {
+      ...TodoFragment
+    }
+  }
+
+  ${TodoFragment}
+`;
+```
+
+### Reading on
+
+There are some more utilities that `@urql/core` exports. [All of them are listed in the API docs for
+it.](../api/core.md)
+
+[Read more about the `@urql/core` library on the "Core Package"
+page.](https://formidable.com/open-source/urql/docs/concepts/core-package/)
+
+
diff --git a/docs/graphcache/computed-queries.md b/docs/graphcache/computed-queries.md
index 9bc593cd..307fdccc 100644
--- a/docs/graphcache/computed-queries.md
+++ b/docs/graphcache/computed-queries.md
@@ -143,7 +143,7 @@ The store allows the user to also read a fragment for a certain entity, this fun
 accepts a `fragment` and an `id`. This looks like the following.
 
 ```js
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 
 const data = cache.readFragment(
   gql`
@@ -157,7 +157,7 @@ const data = cache.readFragment(
 ```
 
 > **Note:** In the above example, we've used
-> [graphql-tag](https://github.com/apollographql/graphql-tag) because `writeFragment` only accepts
+> [the `gql` tag function](../api/core.md#gql) because `readFragment` only accepts
 > GraphQL `DocumentNode`s as inputs, and not strings.
 
 This way we'll get the Todo with id 1 and the relevant data we are asking for in the
diff --git a/docs/graphcache/custom-updates.md b/docs/graphcache/custom-updates.md
index 18f6cb52..39917567 100644
--- a/docs/graphcache/custom-updates.md
+++ b/docs/graphcache/custom-updates.md
@@ -104,7 +104,7 @@ fragment. The second argument is the data that should be written to the cache. T
 contain `id` or another field if the type has a custom `keys` configuration.
 
 ```js
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 
 cache.writeFragment(
   gql`
@@ -121,7 +121,7 @@ cache.writeFragment(
 ```
 
 > **Note:** In the above example, we've used
-> [graphql-tag](https://github.com/apollographql/graphql-tag) because `writeFragment` only accepts
+> [the `gql` tag function](../api/core.md#gql) because `writeFragment` only accepts
 > GraphQL `DocumentNode`s as inputs, and not strings.
 
 This can be useful for instance if we have a mutation that doesn't return the type that the GraphQL
@@ -130,7 +130,7 @@ to access the updated `Todo`. In such a case `cache.writeFragment` allows us to
 manually:
 
 ```js
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 
 const cache = cacheExchange({
   updates: {
diff --git a/exchanges/auth/package.json b/exchanges/auth/package.json
index 904eada9..4d80c19e 100644
--- a/exchanges/auth/package.json
+++ b/exchanges/auth/package.json
@@ -58,8 +58,7 @@
     "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
   },
   "devDependencies": {
-    "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.1"
+    "graphql": "^15.4.0"
   },
   "publishConfig": {
     "access": "public"
diff --git a/exchanges/execute/package.json b/exchanges/execute/package.json
index dd630971..bb620af2 100644
--- a/exchanges/execute/package.json
+++ b/exchanges/execute/package.json
@@ -58,8 +58,7 @@
     "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
   },
   "devDependencies": {
-    "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.1"
+    "graphql": "^15.4.0"
   },
   "publishConfig": {
     "access": "public"
diff --git a/exchanges/graphcache/package.json b/exchanges/graphcache/package.json
index 9ac2d571..6d507da1 100644
--- a/exchanges/graphcache/package.json
+++ b/exchanges/graphcache/package.json
@@ -72,8 +72,7 @@
     "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
   },
   "devDependencies": {
-    "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.1"
+    "graphql": "^15.4.0"
   },
   "publishConfig": {
     "access": "public"
diff --git a/exchanges/graphcache/src/ast/index.ts b/exchanges/graphcache/src/ast/index.ts
index 2bef59f5..4183a935 100644
--- a/exchanges/graphcache/src/ast/index.ts
+++ b/exchanges/graphcache/src/ast/index.ts
@@ -1,4 +1,5 @@
 export * from './variables';
 export * from './traversal';
+export * from './schema';
 export * from './schemaPredicates';
 export * from './node';
diff --git a/exchanges/graphcache/src/ast/schema.ts b/exchanges/graphcache/src/ast/schema.ts
new file mode 100644
index 00000000..82f526d7
--- /dev/null
+++ b/exchanges/graphcache/src/ast/schema.ts
@@ -0,0 +1,107 @@
+import {
+  IntrospectionQuery,
+  IntrospectionInputValue,
+  IntrospectionTypeRef,
+  IntrospectionType,
+} from 'graphql';
+
+export interface SchemaField {
+  name: string;
+  type: IntrospectionTypeRef;
+  args: Record<string, IntrospectionInputValue>;
+}
+
+export interface SchemaObject {
+  name: string;
+  kind: 'INTERFACE' | 'OBJECT';
+  interfaces: Record<string, unknown>;
+  fields: Record<string, SchemaField>;
+}
+
+export interface SchemaUnion {
+  name: string;
+  kind: 'UNION';
+  types: Record<string, unknown>;
+}
+
+export interface SchemaIntrospector {
+  query: string | null;
+  mutation: string | null;
+  subscription: string | null;
+  types: Record<string, SchemaObject | SchemaUnion>;
+  isSubType(abstract: string, possible: string): boolean;
+}
+
+export const buildClientSchema = ({
+  __schema,
+}: IntrospectionQuery): SchemaIntrospector => {
+  const typemap: Record<string, SchemaObject | SchemaUnion> = {};
+
+  const buildNameMap = <T extends { name: string }>(
+    arr: ReadonlyArray<T>
+  ): { [name: string]: T } => {
+    const map: Record<string, T> = {};
+    for (let i = 0; i < arr.length; i++) map[arr[i].name] = arr[i];
+    return map;
+  };
+
+  const buildType = (
+    type: IntrospectionType
+  ): SchemaObject | SchemaUnion | void => {
+    switch (type.kind) {
+      case 'OBJECT':
+      case 'INTERFACE':
+        return {
+          name: type.name,
+          kind: type.kind as 'OBJECT' | 'INTERFACE',
+          interfaces: buildNameMap(type.interfaces || []),
+          fields: buildNameMap(
+            type.fields.map(field => ({
+              name: field.name,
+              type: field.type,
+              args: buildNameMap(field.args),
+            }))
+          ),
+        } as SchemaObject;
+      case 'UNION':
+        return {
+          name: type.name,
+          kind: type.kind as 'UNION',
+          types: buildNameMap(type.possibleTypes || []),
+        } as SchemaUnion;
+    }
+  };
+
+  for (let i = 0; i < __schema.types.length; i++) {
+    const type = __schema.types[i];
+    if (type && type.name) {
+      const out = buildType(type);
+      if (out) typemap[type.name] = out;
+    }
+  }
+
+  return {
+    query: __schema.queryType ? __schema.queryType.name : null,
+    mutation: __schema.mutationType ? __schema.mutationType.name : null,
+    subscription: __schema.subscriptionType
+      ? __schema.subscriptionType.name
+      : null,
+    types: typemap,
+    isSubType(abstract: string, possible: string) {
+      const abstractType = typemap[abstract];
+      const possibleType = typemap[possible];
+      if (!abstractType || !possibleType) {
+        return false;
+      } else if (abstractType.kind === 'UNION') {
+        return !!abstractType.types[possible];
+      } else if (
+        abstractType.kind !== 'OBJECT' &&
+        possibleType.kind === 'OBJECT'
+      ) {
+        return !!possibleType.interfaces[abstract];
+      } else {
+        return abstract === possible;
+      }
+    },
+  };
+};
diff --git a/exchanges/graphcache/src/ast/schemaPredicates.ts b/exchanges/graphcache/src/ast/schemaPredicates.ts
index 4b5f2d3e..777de331 100644
--- a/exchanges/graphcache/src/ast/schemaPredicates.ts
+++ b/exchanges/graphcache/src/ast/schemaPredicates.ts
@@ -1,18 +1,9 @@
-import {
-  isNullableType,
-  isListType,
-  isNonNullType,
-  InlineFragmentNode,
-  FragmentDefinitionNode,
-  GraphQLSchema,
-  GraphQLAbstractType,
-  GraphQLObjectType,
-  GraphQLInterfaceType,
-  GraphQLUnionType,
-} from 'graphql';
+import { InlineFragmentNode, FragmentDefinitionNode } from 'graphql';
 
 import { warn, invariant } from '../helpers/help';
 import { getTypeCondition } from './node';
+import { SchemaIntrospector, SchemaObject } from './schema';
+
 import {
   KeyingConfig,
   UpdateResolver,
@@ -23,28 +14,29 @@ import {
 const BUILTIN_FIELD_RE = /^__/;
 
 export const isFieldNullable = (
-  schema: GraphQLSchema,
+  schema: SchemaIntrospector,
   typename: string,
   fieldName: string
 ): boolean => {
   if (BUILTIN_FIELD_RE.test(fieldName)) return true;
   const field = getField(schema, typename, fieldName);
-  return !!field && isNullableType(field.type);
+  return !!field && field.type.kind !== 'NON_NULL';
 };
 
 export const isListNullable = (
-  schema: GraphQLSchema,
+  schema: SchemaIntrospector,
   typename: string,
   fieldName: string
 ): boolean => {
   const field = getField(schema, typename, fieldName);
   if (!field) return false;
-  const ofType = isNonNullType(field.type) ? field.type.ofType : field.type;
-  return isListType(ofType) && isNullableType(ofType.ofType);
+  const ofType =
+    field.type.kind === 'NON_NULL' ? field.type.ofType : field.type;
+  return ofType.kind === 'LIST' && ofType.ofType.kind !== 'NON_NULL';
 };
 
 export const isFieldAvailableOnType = (
-  schema: GraphQLSchema,
+  schema: SchemaIntrospector,
   typename: string,
   fieldName: string
 ): boolean => {
@@ -53,35 +45,31 @@ export const isFieldAvailableOnType = (
 };
 
 export const isInterfaceOfType = (
-  schema: GraphQLSchema,
+  schema: SchemaIntrospector,
   node: InlineFragmentNode | FragmentDefinitionNode,
   typename: string | void
 ): boolean => {
   if (!typename) return false;
   const typeCondition = getTypeCondition(node);
   if (!typeCondition || typename === typeCondition) return true;
-
-  const abstractType = schema.getType(typeCondition);
-  const objectType = schema.getType(typename);
-
-  if (abstractType instanceof GraphQLObjectType) {
-    return abstractType === objectType;
-  }
-
-  expectAbstractType(abstractType, typeCondition);
-  expectObjectType(objectType, typename);
-  return schema.isPossibleType(abstractType, objectType);
+  if (
+    schema.types[typeCondition] &&
+    schema.types[typeCondition].kind === 'OBJECT'
+  )
+    return typeCondition === typename;
+  expectAbstractType(schema, typeCondition!);
+  expectObjectType(schema, typename!);
+  return schema.isSubType(typeCondition, typename);
 };
 
 const getField = (
-  schema: GraphQLSchema,
+  schema: SchemaIntrospector,
   typename: string,
   fieldName: string
 ) => {
-  const object = schema.getType(typename);
-  expectObjectType(object, typename);
-
-  const field = object.getFields()[fieldName];
+  expectObjectType(schema, typename);
+  const object = schema.types[typename] as SchemaObject;
+  const field = object.fields[fieldName];
   if (!field) {
     warn(
       'Invalid field: The field `' +
@@ -98,12 +86,9 @@ const getField = (
   return field;
 };
 
-function expectObjectType(
-  x: any,
-  typename: string
-): asserts x is GraphQLObjectType {
+function expectObjectType(schema: SchemaIntrospector, typename: string) {
   invariant(
-    x instanceof GraphQLObjectType,
+    schema.types[typename] && schema.types[typename].kind === 'OBJECT',
     'Invalid Object type: The type `' +
       typename +
       '` is not an object in the defined schema, ' +
@@ -112,12 +97,11 @@ function expectObjectType(
   );
 }
 
-function expectAbstractType(
-  x: any,
-  typename: string
-): asserts x is GraphQLAbstractType {
+function expectAbstractType(schema: SchemaIntrospector, typename: string) {
   invariant(
-    x instanceof GraphQLInterfaceType || x instanceof GraphQLUnionType,
+    schema.types[typename] &&
+      (schema.types[typename].kind === 'INTERFACE' ||
+        schema.types[typename].kind === 'UNION'),
     'Invalid Abstract type: The type `' +
       typename +
       '` is not an Interface or Union type in the defined schema, ' +
@@ -127,13 +111,12 @@ function expectAbstractType(
 }
 
 export function expectValidKeyingConfig(
-  schema: GraphQLSchema,
+  schema: SchemaIntrospector,
   keys: KeyingConfig
 ): void {
   if (process.env.NODE_ENV !== 'production') {
-    const types = schema.getTypeMap();
     for (const key in keys) {
-      if (!types[key]) {
+      if (!schema.types[key]) {
         warn(
           'Invalid Object type: The type `' +
             key +
@@ -146,39 +129,43 @@ export function expectValidKeyingConfig(
 }
 
 export function expectValidUpdatesConfig(
-  schema: GraphQLSchema,
+  schema: SchemaIntrospector,
   updates: Record<string, Record<string, UpdateResolver>>
 ): void {
   if (process.env.NODE_ENV === 'production') {
     return;
   }
 
-  const mutation = schema.getMutationType();
-  const subscription = schema.getSubscriptionType();
-  const mutationFields = mutation ? mutation.getFields() : {};
-  const subscriptionFields = subscription ? subscription.getFields() : {};
-  const givenMutations = (mutation && updates[mutation.name]) || {};
-  const givenSubscription = (subscription && updates[subscription.name]) || {};
-
-  for (const fieldName in givenMutations) {
-    if (mutationFields[fieldName] === undefined) {
-      warn(
-        'Invalid mutation field: `' +
-          fieldName +
-          '` is not in the defined schema, but the `updates.Mutation` option is referencing it.',
-        21
-      );
+  if (schema.mutation) {
+    const mutationFields = (schema.types[schema.mutation] as SchemaObject)
+      .fields;
+    const givenMutations = updates[schema.mutation] || {};
+    for (const fieldName in givenMutations) {
+      if (mutationFields[fieldName] === undefined) {
+        warn(
+          'Invalid mutation field: `' +
+            fieldName +
+            '` is not in the defined schema, but the `updates.Mutation` option is referencing it.',
+          21
+        );
+      }
     }
   }
 
-  for (const fieldName in givenSubscription) {
-    if (subscriptionFields[fieldName] === undefined) {
-      warn(
-        'Invalid subscription field: `' +
-          fieldName +
-          '` is not in the defined schema, but the `updates.Subscription` option is referencing it.',
-        22
-      );
+  if (schema.subscription) {
+    const subscriptionFields = (schema.types[
+      schema.subscription
+    ] as SchemaObject).fields;
+    const givenSubscription = updates[schema.subscription] || {};
+    for (const fieldName in givenSubscription) {
+      if (subscriptionFields[fieldName] === undefined) {
+        warn(
+          'Invalid subscription field: `' +
+            fieldName +
+            '` is not in the defined schema, but the `updates.Subscription` option is referencing it.',
+          22
+        );
+      }
     }
   }
 }
@@ -191,19 +178,18 @@ function warnAboutResolver(name: string): void {
 }
 
 export function expectValidResolversConfig(
-  schema: GraphQLSchema,
+  schema: SchemaIntrospector,
   resolvers: ResolverConfig
 ): void {
   if (process.env.NODE_ENV === 'production') {
     return;
   }
 
-  const validTypes = schema.getTypeMap();
   for (const key in resolvers) {
     if (key === 'Query') {
-      const queryType = schema.getQueryType();
-      if (queryType) {
-        const validQueries = queryType.getFields();
+      if (schema.query) {
+        const validQueries = (schema.types[schema.query] as SchemaObject)
+          .fields;
         for (const resolverQuery in resolvers.Query) {
           if (!validQueries[resolverQuery]) {
             warnAboutResolver('Query.' + resolverQuery);
@@ -213,12 +199,10 @@ export function expectValidResolversConfig(
         warnAboutResolver('Query');
       }
     } else {
-      if (!validTypes[key]) {
+      if (!schema.types[key]) {
         warnAboutResolver(key);
       } else {
-        const validTypeProperties = (schema.getType(
-          key
-        ) as GraphQLObjectType).getFields();
+        const validTypeProperties = (schema.types[key] as SchemaObject).fields;
         for (const resolverProperty in resolvers[key]) {
           if (!validTypeProperties[resolverProperty]) {
             warnAboutResolver(key + '.' + resolverProperty);
@@ -230,23 +214,23 @@ export function expectValidResolversConfig(
 }
 
 export function expectValidOptimisticMutationsConfig(
-  schema: GraphQLSchema,
+  schema: SchemaIntrospector,
   optimisticMutations: OptimisticMutationConfig
 ): void {
   if (process.env.NODE_ENV === 'production') {
     return;
   }
 
-  const validMutations = schema.getMutationType()
-    ? (schema.getMutationType() as GraphQLObjectType).getFields()
-    : {};
-
-  for (const mutation in optimisticMutations) {
-    if (!validMutations[mutation]) {
-      warn(
-        `Invalid optimistic mutation field: \`${mutation}\` is not a mutation field in the defined schema, but the \`optimistic\` option is referencing it.`,
-        24
-      );
+  if (schema.mutation) {
+    const validMutations = (schema.types[schema.mutation] as SchemaObject)
+      .fields;
+    for (const mutation in optimisticMutations) {
+      if (!validMutations[mutation]) {
+        warn(
+          `Invalid optimistic mutation field: \`${mutation}\` is not a mutation field in the defined schema, but the \`optimistic\` option is referencing it.`,
+          24
+        );
+      }
     }
   }
 }
diff --git a/exchanges/graphcache/src/store/store.ts b/exchanges/graphcache/src/store/store.ts
index 65d7bccf..c4419843 100644
--- a/exchanges/graphcache/src/store/store.ts
+++ b/exchanges/graphcache/src/store/store.ts
@@ -1,9 +1,4 @@
-import {
-  buildClientSchema,
-  DocumentNode,
-  IntrospectionQuery,
-  GraphQLSchema,
-} from 'graphql';
+import { DocumentNode, IntrospectionQuery } from 'graphql';
 
 import { TypedDocumentNode, formatDocument, createRequest } from '@urql/core';
 
@@ -27,7 +22,15 @@ import { writeFragment, startWrite } from '../operations/write';
 import { invalidateEntity } from '../operations/invalidate';
 import { keyOfField } from './keys';
 import * as InMemoryData from './data';
-import * as SchemaPredicates from '../ast/schemaPredicates';
+
+import {
+  SchemaIntrospector,
+  buildClientSchema,
+  expectValidKeyingConfig,
+  expectValidUpdatesConfig,
+  expectValidResolversConfig,
+  expectValidOptimisticMutationsConfig,
+} from '../ast';
 
 type RootField = 'query' | 'mutation' | 'subscription';
 
@@ -46,7 +49,7 @@ export class Store implements Cache {
   updates: Record<string, Record<string, UpdateResolver>>;
   optimisticMutations: OptimisticMutationConfig;
   keys: KeyingConfig;
-  schema?: GraphQLSchema;
+  schema?: SchemaIntrospector;
 
   rootFields: { query: string; mutation: string; subscription: string };
   rootNames: { [name: string]: RootField };
@@ -63,14 +66,9 @@ export class Store implements Cache {
     let subscriptionName = 'Subscription';
     if (opts.schema) {
       const schema = (this.schema = buildClientSchema(opts.schema));
-      const queryType = schema.getQueryType();
-      const mutationType = schema.getMutationType();
-      const subscriptionType = schema.getSubscriptionType();
-      queryName = queryType ? queryType.name : queryName;
-      mutationName = mutationType ? mutationType.name : mutationName;
-      subscriptionName = subscriptionType
-        ? subscriptionType.name
-        : subscriptionName;
+      queryName = schema.query || queryName;
+      mutationName = schema.mutation || mutationName;
+      subscriptionName = schema.subscription || subscriptionName;
     }
 
     this.updates = {
@@ -93,10 +91,10 @@ export class Store implements Cache {
     this.data = InMemoryData.make(queryName);
 
     if (this.schema && process.env.NODE_ENV !== 'production') {
-      SchemaPredicates.expectValidKeyingConfig(this.schema, this.keys);
-      SchemaPredicates.expectValidUpdatesConfig(this.schema, this.updates);
-      SchemaPredicates.expectValidResolversConfig(this.schema, this.resolvers);
-      SchemaPredicates.expectValidOptimisticMutationsConfig(
+      expectValidKeyingConfig(this.schema, this.keys);
+      expectValidUpdatesConfig(this.schema, this.updates);
+      expectValidResolversConfig(this.schema, this.resolvers);
+      expectValidOptimisticMutationsConfig(
         this.schema,
         this.optimisticMutations
       );
diff --git a/exchanges/multipart-fetch/package.json b/exchanges/multipart-fetch/package.json
index 8b2d8859..2a36d33b 100644
--- a/exchanges/multipart-fetch/package.json
+++ b/exchanges/multipart-fetch/package.json
@@ -57,8 +57,7 @@
     "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
   },
   "devDependencies": {
-    "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.1"
+    "graphql": "^15.4.0"
   },
   "publishConfig": {
     "access": "public"
diff --git a/exchanges/persisted-fetch/package.json b/exchanges/persisted-fetch/package.json
index 28f7dfb4..2d43422b 100644
--- a/exchanges/persisted-fetch/package.json
+++ b/exchanges/persisted-fetch/package.json
@@ -56,8 +56,7 @@
     "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
   },
   "devDependencies": {
-    "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.1"
+    "graphql": "^15.4.0"
   },
   "publishConfig": {
     "access": "public"
diff --git a/exchanges/populate/package.json b/exchanges/populate/package.json
index 4b4018f3..1c18ed7c 100644
--- a/exchanges/populate/package.json
+++ b/exchanges/populate/package.json
@@ -56,8 +56,7 @@
     "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
   },
   "devDependencies": {
-    "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.1"
+    "graphql": "^15.4.0"
   },
   "publishConfig": {
     "access": "public"
diff --git a/package.json b/package.json
index 5fa285c0..21256300 100644
--- a/package.json
+++ b/package.json
@@ -74,7 +74,6 @@
     "execa": "^4.1.0",
     "glob": "^7.1.6",
     "graphql": "^15.4.0",
-    "graphql-tag": "^2.11.0",
     "husky": "^4.3.0",
     "invariant": "^2.2.4",
     "jest": "^26.6.3",
diff --git a/packages/core/package.json b/packages/core/package.json
index fce61ba7..263f14ad 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -56,8 +56,7 @@
     "preset": "../../scripts/jest/preset"
   },
   "devDependencies": {
-    "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.1"
+    "graphql": "^15.4.0"
   },
   "peerDependencies": {
     "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
diff --git a/packages/core/src/exchanges/cache.ts b/packages/core/src/exchanges/cache.ts
index a3328910..6b12300c 100755
--- a/packages/core/src/exchanges/cache.ts
+++ b/packages/core/src/exchanges/cache.ts
@@ -2,7 +2,7 @@
 import { filter, map, merge, pipe, share, tap } from 'wonka';
 
 import { Client } from '../client';
-import { Exchange, Operation, OperationResult, ExchangeInput } from '../types';
+import { Exchange, Operation, OperationResult } from '../types';
 
 import {
   makeOperation,
@@ -31,15 +31,6 @@ export const cacheExchange: Exchange = ({ forward, client, dispatchDebug }) => {
     return formattedOperation;
   };
 
-  const handleAfterMutation = afterMutation(
-    resultCache,
-    operationCache,
-    client,
-    dispatchDebug
-  );
-
-  const handleAfterQuery = afterQuery(resultCache, operationCache);
-
   const isOperationCached = (operation: Operation) => {
     const {
       key,
@@ -109,10 +100,52 @@ export const cacheExchange: Exchange = ({ forward, client, dispatchDebug }) => {
       ),
       forward,
       tap(response => {
-        if (response.operation && response.operation.kind === 'mutation') {
-          handleAfterMutation(response);
-        } else if (response.operation && response.operation.kind === 'query') {
-          handleAfterQuery(response);
+        let { operation } = response;
+        if (!operation) return;
+
+        const typenames = collectTypesFromResponse(response.data).concat(
+          operation.context.additionalTypenames || []
+        );
+
+        // Invalidates the cache given a mutation's response
+        if (response.operation.kind === 'mutation') {
+          const pendingOperations = new Set<number>();
+
+          dispatchDebug({
+            type: 'cacheInvalidation',
+            message: `The following typenames have been invalidated: ${typenames}`,
+            operation,
+            data: { typenames, response },
+          });
+
+          for (let i = 0; i < typenames.length; i++) {
+            const typeName = typenames[i];
+            const operations =
+              operationCache[typeName] ||
+              (operationCache[typeName] = new Set());
+            operations.forEach(key => {
+              pendingOperations.add(key);
+            });
+            operations.clear();
+          }
+
+          pendingOperations.forEach(key => {
+            if (resultCache.has(key)) {
+              operation = (resultCache.get(key) as OperationResult).operation;
+              resultCache.delete(key);
+              reexecuteOperation(client, operation);
+            }
+          });
+          // Mark typenames on typenameInvalidate for early invalidation
+        } else if (operation.kind === 'query' && response.data) {
+          resultCache.set(operation.key, response);
+          for (let i = 0; i < typenames.length; i++) {
+            const typeName = typenames[i];
+            const operations =
+              operationCache[typeName] ||
+              (operationCache[typeName] = new Set());
+            operations.add(operation.key);
+          }
         }
       })
     );
@@ -130,67 +163,3 @@ const reexecuteOperation = (client: Client, operation: Operation) => {
     })
   );
 };
-
-// Invalidates the cache given a mutation's response
-export const afterMutation = (
-  resultCache: ResultCache,
-  operationCache: OperationCache,
-  client: Client,
-  dispatchDebug: ExchangeInput['dispatchDebug']
-) => (response: OperationResult) => {
-  const pendingOperations = new Set<number>();
-  const { additionalTypenames } = response.operation.context;
-
-  const typenames = [
-    ...collectTypesFromResponse(response.data),
-    ...(additionalTypenames || []),
-  ];
-
-  dispatchDebug({
-    type: 'cacheInvalidation',
-    message: `The following typenames have been invalidated: ${typenames}`,
-    operation: response.operation,
-    data: { typenames, response },
-  });
-
-  typenames.forEach(typeName => {
-    const operations =
-      operationCache[typeName] || (operationCache[typeName] = new Set());
-    operations.forEach(key => {
-      pendingOperations.add(key);
-    });
-    operations.clear();
-  });
-
-  pendingOperations.forEach(key => {
-    if (resultCache.has(key)) {
-      const operation = (resultCache.get(key) as OperationResult).operation;
-      resultCache.delete(key);
-      reexecuteOperation(client, operation);
-    }
-  });
-};
-
-// Mark typenames on typenameInvalidate for early invalidation
-const afterQuery = (
-  resultCache: ResultCache,
-  operationCache: OperationCache
-) => (response: OperationResult) => {
-  const { operation, data, error } = response;
-  const { additionalTypenames } = operation.context;
-
-  if (data === undefined || data === null) {
-    return;
-  }
-
-  resultCache.set(operation.key, { operation, data, error });
-
-  [
-    ...collectTypesFromResponse(response.data),
-    ...(additionalTypenames || []),
-  ].forEach(typeName => {
-    const operations =
-      operationCache[typeName] || (operationCache[typeName] = new Set());
-    operations.add(operation.key);
-  });
-};
diff --git a/packages/core/src/gql.ts b/packages/core/src/gql.ts
new file mode 100644
index 00000000..7819a09c
--- /dev/null
+++ b/packages/core/src/gql.ts
@@ -0,0 +1,64 @@
+/* eslint-disable prefer-rest-params */
+import { TypedDocumentNode } from '@graphql-typed-document-node/core';
+import { Source, Location, DocumentNode, Kind, print } from 'graphql';
+import { keyDocument } from './utils';
+
+type WritableDocumentNode = {
+  -readonly [K in keyof DocumentNode]: DocumentNode[K];
+};
+
+function gql<Data = any, Variables = object>(
+  strings: TemplateStringsArray,
+  ...interpolations: Array<TypedDocumentNode | DocumentNode | string>
+): TypedDocumentNode<Data, Variables>;
+
+function gql<Data = any, Variables = object>(
+  string: string
+): TypedDocumentNode<Data, Variables>;
+
+function gql(/* arguments */) {
+  let body: string = Array.isArray(arguments[0])
+    ? arguments[0][0]
+    : arguments[0] || '';
+  for (let i = 1; i < arguments.length; i++) {
+    const value = arguments[i];
+    body +=
+      value && value.kind === Kind.DOCUMENT
+        ? value.loc
+          ? value.loc.source.body
+          : print(value)
+        : value;
+    body += arguments[0][i];
+  }
+
+  const doc = keyDocument(body);
+  if (process.env.NODE_ENV !== 'production') {
+    const fragmentNames = new Set();
+    for (let i = 0; i < doc.definitions.length; i++) {
+      const definition = doc.definitions[i];
+      if (definition.kind === Kind.FRAGMENT_DEFINITION) {
+        const name = definition.name.value;
+        if (fragmentNames.has(name)) {
+          console.warn(
+            '[WARNING: Duplicate Fragment] A fragment with name `' +
+              name +
+              '` already exists in this document.\n' +
+              'While fragment names may not be unique across your source, each name must be unique per document.'
+          );
+        } else {
+          fragmentNames.add(name);
+        }
+      }
+    }
+  }
+
+  (doc as WritableDocumentNode).loc = {
+    start: 0,
+    end: body.length,
+    source: new Source(body, 'gql'),
+  } as Location;
+
+  return doc;
+}
+
+export { gql };
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index cd299596..21671c39 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -1,4 +1,5 @@
 export { TypedDocumentNode } from '@graphql-typed-document-node/core';
+export { gql } from './gql';
 
 export * from './client';
 export * from './exchanges';
diff --git a/packages/introspection/package.json b/packages/introspection/package.json
index 68fbee4e..330c20e8 100644
--- a/packages/introspection/package.json
+++ b/packages/introspection/package.json
@@ -48,8 +48,7 @@
     "preset": "../../scripts/jest/preset"
   },
   "devDependencies": {
-    "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.1"
+    "graphql": "^15.4.0"
   },
   "peerDependencies": {
     "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
diff --git a/packages/next-urql/package.json b/packages/next-urql/package.json
index be0eb3ce..41eb9130 100644
--- a/packages/next-urql/package.json
+++ b/packages/next-urql/package.json
@@ -42,7 +42,6 @@
     "enzyme": "^3.11.0",
     "enzyme-adapter-react-16": "^1.15.2",
     "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.3",
     "react": "^17.0.1",
     "react-dom": "^17.0.1",
     "react-is": "^17.0.1"
diff --git a/packages/preact-urql/package.json b/packages/preact-urql/package.json
index b7e9b2db..0542cd15 100644
--- a/packages/preact-urql/package.json
+++ b/packages/preact-urql/package.json
@@ -53,7 +53,6 @@
   "devDependencies": {
     "@testing-library/preact": "^2.0.0",
     "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.1",
     "preact": "^10.5.5"
   },
   "peerDependencies": {
diff --git a/packages/react-urql/package.json b/packages/react-urql/package.json
index e2714801..1ed1db1f 100644
--- a/packages/react-urql/package.json
+++ b/packages/react-urql/package.json
@@ -47,7 +47,6 @@
     "@types/react": "^16.9.56",
     "@types/react-test-renderer": "^16.9.3",
     "graphql": "^15.4.0",
-    "graphql-tag": "^2.10.1",
     "react": "^17.0.1",
     "react-dom": "^17.0.1",
     "react-is": "^17.0.1",
diff --git a/yarn.lock b/yarn.lock
index ad48d3a1..29f7b95e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2842,7 +2842,7 @@
     "@typescript-eslint/types" "4.6.1"
     eslint-visitor-keys "^2.0.0"
 
-"@urql/devtools@^2.0.2":
+"@urql/devtools@>=2.0.0":
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/@urql/devtools/-/devtools-2.0.2.tgz#0991bc3bb09444b162ef56518b76f64f286954fc"
   integrity sha512-f3gIx4NDOuWdXC54m4VQlLKNtYHl6PITSEqexH6xiktzvH7QcwS5hNPg0Fkki8cas/DZtkmDpwxNV1uRZ5xlvA==
@@ -8123,11 +8123,6 @@ grapheme-splitter@^1.0.4:
   resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
   integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
 
-graphql-tag@^2.10.1, graphql-tag@^2.10.3, graphql-tag@^2.11.0:
-  version "2.11.0"
-  resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.11.0.tgz#1deb53a01c46a7eb401d6cb59dec86fa1cccbffd"
-  integrity sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA==
-
 graphql@^15.1.0, graphql@^15.4.0:
   version "15.4.0"
   resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.4.0.tgz#e459dea1150da5a106486ba7276518b5295a4347"


Test patch:
diff --git a/exchanges/execute/src/execute.test.ts b/exchanges/execute/src/execute.test.ts
index 04f85a17..e80ae8e5 100644
--- a/exchanges/execute/src/execute.test.ts
+++ b/exchanges/execute/src/execute.test.ts
@@ -1,4 +1,13 @@
-jest.mock('graphql');
+jest.mock('graphql', () => {
+  const graphql = jest.requireActual('graphql');
+
+  return {
+    __esModule: true,
+    ...graphql,
+    print: jest.fn(a => a as any),
+    execute: jest.fn(() => ({ key: 'value' })),
+  };
+});
 
 import { fetchExchange } from 'urql';
 import { executeExchange } from './execute';
@@ -31,19 +40,18 @@ const exchangeArgs = {
 const expectedOperationName = getOperationName(queryOperation.query);
 
 const fetchMock = (global as any).fetch as jest.Mock;
-afterEach(() => {
-  fetchMock.mockClear();
-});
-
 const mockHttpResponseData = { key: 'value' };
 
-beforeEach(jest.clearAllMocks);
-
 beforeEach(() => {
+  jest.clearAllMocks();
   mocked(print).mockImplementation(a => a as any);
   mocked(execute).mockResolvedValue({ data: mockHttpResponseData });
 });
 
+afterEach(() => {
+  fetchMock.mockClear();
+});
+
 describe('on operation', () => {
   it('calls execute with args', async () => {
     const context = 'USER_ID=123';
diff --git a/exchanges/graphcache/src/ast/schemaPredicates.test.ts b/exchanges/graphcache/src/ast/schemaPredicates.test.ts
index 0b7b7d8f..0d31ae72 100644
--- a/exchanges/graphcache/src/ast/schemaPredicates.test.ts
+++ b/exchanges/graphcache/src/ast/schemaPredicates.test.ts
@@ -1,5 +1,6 @@
-import { Kind, InlineFragmentNode, buildClientSchema } from 'graphql';
+import { Kind, InlineFragmentNode } from 'graphql';
 import { mocked } from 'ts-jest/utils';
+import { buildClientSchema } from './schema';
 import * as SchemaPredicates from './schemaPredicates';
 
 describe('SchemaPredicates', () => {
diff --git a/exchanges/graphcache/src/ast/traversal.test.ts b/exchanges/graphcache/src/ast/traversal.test.ts
index cb69b7d7..86479f85 100644
--- a/exchanges/graphcache/src/ast/traversal.test.ts
+++ b/exchanges/graphcache/src/ast/traversal.test.ts
@@ -1,4 +1,4 @@
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { getSelectionSet } from './node';
 
 import { getMainOperation, shouldInclude } from './traversal';
diff --git a/exchanges/graphcache/src/ast/variables.test.ts b/exchanges/graphcache/src/ast/variables.test.ts
index 9f06c511..cc387049 100644
--- a/exchanges/graphcache/src/ast/variables.test.ts
+++ b/exchanges/graphcache/src/ast/variables.test.ts
@@ -1,4 +1,4 @@
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { getMainOperation } from './traversal';
 import { normalizeVariables, filterVariables } from './variables';
 
diff --git a/exchanges/graphcache/src/cacheExchange.test.ts b/exchanges/graphcache/src/cacheExchange.test.ts
index 605dcfc7..f776d1e6 100644
--- a/exchanges/graphcache/src/cacheExchange.test.ts
+++ b/exchanges/graphcache/src/cacheExchange.test.ts
@@ -1,6 +1,5 @@
-import gql from 'graphql-tag';
-
 import {
+  gql,
   createClient,
   ExchangeIO,
   Operation,
diff --git a/exchanges/graphcache/src/extras/relayPagination.test.ts b/exchanges/graphcache/src/extras/relayPagination.test.ts
index 7b74d79a..466638be 100644
--- a/exchanges/graphcache/src/extras/relayPagination.test.ts
+++ b/exchanges/graphcache/src/extras/relayPagination.test.ts
@@ -1,4 +1,4 @@
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { query, write } from '../operations';
 import { Store } from '../store';
 import { relayPagination } from './relayPagination';
diff --git a/exchanges/graphcache/src/extras/simplePagination.test.ts b/exchanges/graphcache/src/extras/simplePagination.test.ts
index 51ffdf70..4cf07080 100644
--- a/exchanges/graphcache/src/extras/simplePagination.test.ts
+++ b/exchanges/graphcache/src/extras/simplePagination.test.ts
@@ -1,4 +1,4 @@
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { query, write } from '../operations';
 import { Store } from '../store';
 import { simplePagination } from './simplePagination';
diff --git a/exchanges/graphcache/src/offlineExchange.test.ts b/exchanges/graphcache/src/offlineExchange.test.ts
index 5c5dea54..f10c0557 100644
--- a/exchanges/graphcache/src/offlineExchange.test.ts
+++ b/exchanges/graphcache/src/offlineExchange.test.ts
@@ -1,4 +1,5 @@
 import {
+  gql,
   createClient,
   ExchangeIO,
   Operation,
@@ -6,7 +7,6 @@ import {
   formatDocument,
 } from '@urql/core';
 
-import gql from 'graphql-tag';
 import { pipe, map, makeSubject, tap, publish } from 'wonka';
 import { offlineExchange } from './offlineExchange';
 
diff --git a/exchanges/graphcache/src/operations/query.test.ts b/exchanges/graphcache/src/operations/query.test.ts
index be81ebc3..94cdbb29 100644
--- a/exchanges/graphcache/src/operations/query.test.ts
+++ b/exchanges/graphcache/src/operations/query.test.ts
@@ -1,6 +1,6 @@
 /* eslint-disable @typescript-eslint/no-var-requires */
 
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { minifyIntrospectionQuery } from '@urql/introspection';
 
 import { Store } from '../store';
diff --git a/exchanges/graphcache/src/operations/write.test.ts b/exchanges/graphcache/src/operations/write.test.ts
index b6e4a4d4..901431d0 100644
--- a/exchanges/graphcache/src/operations/write.test.ts
+++ b/exchanges/graphcache/src/operations/write.test.ts
@@ -1,6 +1,6 @@
 /* eslint-disable @typescript-eslint/no-var-requires */
 
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { minifyIntrospectionQuery } from '@urql/introspection';
 
 import { write } from './write';
diff --git a/exchanges/graphcache/src/store/store.test.ts b/exchanges/graphcache/src/store/store.test.ts
index 43c81648..24276459 100644
--- a/exchanges/graphcache/src/store/store.test.ts
+++ b/exchanges/graphcache/src/store/store.test.ts
@@ -1,8 +1,7 @@
 /* eslint-disable @typescript-eslint/no-var-requires */
 
-import gql from 'graphql-tag';
 import { minifyIntrospectionQuery } from '@urql/introspection';
-import { maskTypename } from '@urql/core';
+import { gql, maskTypename } from '@urql/core';
 import { mocked } from 'ts-jest/utils';
 
 import { Data, StorageAdapter } from '../types';
diff --git a/exchanges/graphcache/src/test-utils/examples-1.test.ts b/exchanges/graphcache/src/test-utils/examples-1.test.ts
index 4378c408..1d324473 100644
--- a/exchanges/graphcache/src/test-utils/examples-1.test.ts
+++ b/exchanges/graphcache/src/test-utils/examples-1.test.ts
@@ -1,4 +1,4 @@
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { query, write, writeOptimistic } from '../operations';
 import * as InMemoryData from '../store/data';
 import { Store } from '../store';
diff --git a/exchanges/graphcache/src/test-utils/examples-2.test.ts b/exchanges/graphcache/src/test-utils/examples-2.test.ts
index 982f3f6c..6b5f7e6d 100644
--- a/exchanges/graphcache/src/test-utils/examples-2.test.ts
+++ b/exchanges/graphcache/src/test-utils/examples-2.test.ts
@@ -1,4 +1,4 @@
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { query, write } from '../operations';
 import { Store } from '../store';
 
diff --git a/exchanges/graphcache/src/test-utils/examples-3.test.ts b/exchanges/graphcache/src/test-utils/examples-3.test.ts
index 7ed83020..775ea0be 100644
--- a/exchanges/graphcache/src/test-utils/examples-3.test.ts
+++ b/exchanges/graphcache/src/test-utils/examples-3.test.ts
@@ -1,4 +1,4 @@
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { query, write } from '../operations';
 import { Store } from '../store';
 
diff --git a/exchanges/graphcache/src/test-utils/suite.test.ts b/exchanges/graphcache/src/test-utils/suite.test.ts
index 1f8958bc..6c0c27ca 100644
--- a/exchanges/graphcache/src/test-utils/suite.test.ts
+++ b/exchanges/graphcache/src/test-utils/suite.test.ts
@@ -1,5 +1,5 @@
 import { DocumentNode } from 'graphql';
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { query, write } from '../operations';
 import { Store } from '../store';
 
diff --git a/exchanges/multipart-fetch/src/__snapshots__/multipartFetchExchange.test.ts.snap b/exchanges/multipart-fetch/src/__snapshots__/multipartFetchExchange.test.ts.snap
index 751c6356..4eb26e8e 100644
--- a/exchanges/multipart-fetch/src/__snapshots__/multipartFetchExchange.test.ts.snap
+++ b/exchanges/multipart-fetch/src/__snapshots__/multipartFetchExchange.test.ts.snap
@@ -17,31 +17,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -49,20 +57,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -72,8 +85,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -83,8 +98,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -99,17 +116,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -120,6 +142,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
@@ -157,31 +195,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -189,20 +235,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -212,8 +263,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -223,8 +276,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -239,17 +294,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -260,6 +320,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
@@ -299,31 +375,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -331,20 +415,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -354,8 +443,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -365,8 +456,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -381,17 +474,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -402,6 +500,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
@@ -443,31 +557,39 @@ Object {
     "kind": "mutation",
     "operationName": "mutation",
     "query": Object {
+      "__key": 3781860981,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "uploadProfilePicture",
           },
           "operation": "mutation",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "picture",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "picture",
                       },
                     },
@@ -475,20 +597,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "uploadProfilePicture",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "location",
                       },
                       "selectionSet": undefined,
@@ -503,17 +630,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "File",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "picture",
                 },
               },
@@ -524,6 +656,20 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 134,
+        "source": Source {
+          "body": "
+    mutation uploadProfilePicture($picture: File) {
+      uploadProfilePicture(picture: $picture) {
+        location
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
@@ -567,31 +713,39 @@ Object {
     "kind": "mutation",
     "operationName": "mutation",
     "query": Object {
+      "__key": 1193185401,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "uploadProfilePictures",
           },
           "operation": "mutation",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "pictures",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "pictures",
                       },
                     },
@@ -599,20 +753,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "uploadProfilePicture",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "location",
                       },
                       "selectionSet": undefined,
@@ -627,20 +786,26 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "ListType",
+                "loc": undefined,
                 "type": Object {
                   "kind": "NamedType",
+                  "loc": undefined,
                   "name": Object {
                     "kind": "Name",
+                    "loc": undefined,
                     "value": "File",
                   },
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "pictures",
                 },
               },
@@ -651,6 +816,20 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 140,
+        "source": Source {
+          "body": "
+    mutation uploadProfilePictures($pictures: [File]) {
+      uploadProfilePicture(pictures: $pictures) {
+        location
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
diff --git a/exchanges/multipart-fetch/src/test-utils.ts b/exchanges/multipart-fetch/src/test-utils.ts
index 6b075b7e..492fb402 100644
--- a/exchanges/multipart-fetch/src/test-utils.ts
+++ b/exchanges/multipart-fetch/src/test-utils.ts
@@ -1,10 +1,10 @@
 import {
+  gql,
   GraphQLRequest,
   OperationContext,
   Operation,
   makeOperation,
 } from '@urql/core';
-import gql from 'graphql-tag';
 
 const context: OperationContext = {
   fetchOptions: {
diff --git a/exchanges/persisted-fetch/src/test-utils.ts b/exchanges/persisted-fetch/src/test-utils.ts
index b084a3a1..42c7e527 100644
--- a/exchanges/persisted-fetch/src/test-utils.ts
+++ b/exchanges/persisted-fetch/src/test-utils.ts
@@ -1,10 +1,10 @@
 import {
+  gql,
   GraphQLRequest,
   OperationContext,
   Operation,
   makeOperation,
 } from '@urql/core';
-import gql from 'graphql-tag';
 
 const context: OperationContext = {
   fetchOptions: {
diff --git a/exchanges/populate/src/populateExchange.test.ts b/exchanges/populate/src/populateExchange.test.ts
index 30321c38..33276a51 100644
--- a/exchanges/populate/src/populateExchange.test.ts
+++ b/exchanges/populate/src/populateExchange.test.ts
@@ -7,9 +7,14 @@ import {
   ASTKindToNode,
 } from 'graphql';
 
-import gql from 'graphql-tag';
 import { fromValue, pipe, fromArray, toArray } from 'wonka';
-import { Client, Operation, OperationContext, makeOperation } from '@urql/core';
+import {
+  gql,
+  Client,
+  Operation,
+  OperationContext,
+  makeOperation,
+} from '@urql/core';
 
 import { populateExchange } from './populateExchange';
 
diff --git a/exchanges/refocus/src/refocusExchange.test.ts b/exchanges/refocus/src/refocusExchange.test.ts
index 1be6be6c..e90777d1 100644
--- a/exchanges/refocus/src/refocusExchange.test.ts
+++ b/exchanges/refocus/src/refocusExchange.test.ts
@@ -1,13 +1,13 @@
-import gql from 'graphql-tag';
-
 import { pipe, map, makeSubject, publish, tap } from 'wonka';
 
 import {
+  gql,
   createClient,
   Operation,
   OperationResult,
   ExchangeIO,
 } from '@urql/core';
+
 import { refocusExchange } from './refocusExchange';
 
 const dispatchDebug = jest.fn();
diff --git a/exchanges/request-policy/src/requestPolicyExchange.test.ts b/exchanges/request-policy/src/requestPolicyExchange.test.ts
index e0022e0d..182b27fe 100644
--- a/exchanges/request-policy/src/requestPolicyExchange.test.ts
+++ b/exchanges/request-policy/src/requestPolicyExchange.test.ts
@@ -1,13 +1,13 @@
-import gql from 'graphql-tag';
-
 import { pipe, map, makeSubject, publish, tap } from 'wonka';
 
 import {
+  gql,
   createClient,
   Operation,
   OperationResult,
   ExchangeIO,
 } from '@urql/core';
+
 import { requestPolicyExchange } from './requestPolicyExchange';
 
 const dispatchDebug = jest.fn();
diff --git a/exchanges/retry/src/retryExchange.test.ts b/exchanges/retry/src/retryExchange.test.ts
index 205a6031..e3514b5c 100644
--- a/exchanges/retry/src/retryExchange.test.ts
+++ b/exchanges/retry/src/retryExchange.test.ts
@@ -1,13 +1,13 @@
-import gql from 'graphql-tag';
-
 import { pipe, map, makeSubject, publish, tap } from 'wonka';
 
 import {
+  gql,
   createClient,
   Operation,
   OperationResult,
   ExchangeIO,
 } from '@urql/core';
+
 import { retryExchange } from './retryExchange';
 
 const dispatchDebug = jest.fn();
diff --git a/packages/core/src/client.test.ts b/packages/core/src/client.test.ts
index 125c3b94..a98dd118 100755
--- a/packages/core/src/client.test.ts
+++ b/packages/core/src/client.test.ts
@@ -1,9 +1,9 @@
 import { print } from 'graphql';
-import gql from 'graphql-tag';
 
 /** NOTE: Testing in this file is designed to test both the client and its interaction with default Exchanges */
 
 import { Source, map, pipe, subscribe, filter, toArray, tap } from 'wonka';
+import { gql } from './gql';
 import { Exchange, Operation, OperationResult } from './types';
 import { createClient } from './client';
 import { queryOperation } from './test-utils';
diff --git a/packages/core/src/exchanges/__snapshots__/fetch.test.ts.snap b/packages/core/src/exchanges/__snapshots__/fetch.test.ts.snap
index dac2c060..5ddb86d4 100644
--- a/packages/core/src/exchanges/__snapshots__/fetch.test.ts.snap
+++ b/packages/core/src/exchanges/__snapshots__/fetch.test.ts.snap
@@ -17,31 +17,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -49,20 +57,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -72,8 +85,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -83,8 +98,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -99,17 +116,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -120,6 +142,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
@@ -157,31 +195,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -189,20 +235,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -212,8 +263,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -223,8 +276,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -239,17 +294,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -260,6 +320,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
@@ -299,31 +375,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -331,20 +415,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -354,8 +443,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -365,8 +456,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -381,17 +474,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -402,6 +500,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
diff --git a/packages/core/src/exchanges/__snapshots__/subscription.test.ts.snap b/packages/core/src/exchanges/__snapshots__/subscription.test.ts.snap
index 91058c37..963e943e 100644
--- a/packages/core/src/exchanges/__snapshots__/subscription.test.ts.snap
+++ b/packages/core/src/exchanges/__snapshots__/subscription.test.ts.snap
@@ -17,31 +17,39 @@ Object {
     "kind": "subscription",
     "operationName": "subscription",
     "query": Object {
+      "__key": 2616620656,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "subscribeToUser",
           },
           "operation": "subscription",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "user",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "user",
                       },
                     },
@@ -49,20 +57,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                       "selectionSet": undefined,
@@ -77,17 +90,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
               },
@@ -98,6 +116,20 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 106,
+        "source": Source {
+          "body": "
+    subscription subscribeToUser($user: String) {
+      user(user: $user) {
+        name
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
diff --git a/packages/core/src/exchanges/cache.test.ts b/packages/core/src/exchanges/cache.test.ts
index 8715ee22..2ef9a580 100755
--- a/packages/core/src/exchanges/cache.test.ts
+++ b/packages/core/src/exchanges/cache.test.ts
@@ -20,7 +20,7 @@ import {
   undefinedQueryResponse,
 } from '../test-utils';
 import { Operation, OperationResult, ExchangeInput } from '../types';
-import { afterMutation, cacheExchange } from './cache';
+import { cacheExchange } from './cache';
 
 const reexecuteOperation = jest.fn();
 const dispatchDebug = jest.fn();
@@ -176,63 +176,6 @@ describe('on mutation', () => {
     expect(forwardedOperations.length).toBe(2);
     expect(reexecuteOperation).not.toBeCalled();
   });
-
-  it('retriggers query operation', () => {
-    const typename = 'ExampleType';
-    const resultCache = new Map([[123, queryResponse]]);
-    const operationCache = { [typename]: new Set([123]) };
-
-    afterMutation(
-      resultCache,
-      operationCache,
-      exchangeArgs.client,
-      dispatchDebug
-    )({
-      ...mutationResponse,
-      data: {
-        todos: [
-          {
-            id: 1,
-            __typename: typename,
-          },
-        ],
-      },
-    });
-
-    expect(reexecuteOperation).toBeCalledTimes(1);
-  });
-
-  it('retriggers query operation with additionalTypenames', () => {
-    const typename = 'ExampleType';
-    const resultCache = new Map([[123, queryResponse]]);
-    const operationCache = { [`${typename}-2`]: new Set([123]) };
-
-    afterMutation(
-      resultCache,
-      operationCache,
-      exchangeArgs.client,
-      dispatchDebug
-    )({
-      ...mutationResponse,
-      operation: {
-        ...mutationResponse.operation,
-        context: {
-          ...mutationResponse.operation.context,
-          additionalTypenames: [`${typename}-2`],
-        },
-      },
-      data: {
-        todos: [
-          {
-            id: 1,
-            __typename: typename,
-          },
-        ],
-      },
-    });
-
-    expect(reexecuteOperation).toBeCalledTimes(1);
-  });
 });
 
 describe('on subscription', () => {
diff --git a/packages/core/src/gql.test.ts b/packages/core/src/gql.test.ts
new file mode 100644
index 00000000..90c71bf8
--- /dev/null
+++ b/packages/core/src/gql.test.ts
@@ -0,0 +1,60 @@
+import { Source, parse, print } from 'graphql';
+import { gql } from './gql';
+import { keyDocument } from './utils';
+
+it('parses GraphQL Documents', () => {
+  expect(gql('{ test }').definitions).toEqual(
+    parse('{ test }', { noLocation: true }).definitions
+  );
+  expect(gql('{ test }')).toBe(keyDocument('{ test }'));
+
+  expect(gql('{ test }').loc).toEqual({
+    start: 0,
+    end: 8,
+    source: new Source('{ test }', 'gql'),
+  });
+});
+
+it('interpolates nested GraphQL Documents', () => {
+  expect(
+    print(
+      gql`
+        query {
+          ...Query
+        }
+
+        ${gql`
+          fragment Query on Query {
+            field
+          }
+        `}
+      `
+    )
+  ).toMatchInlineSnapshot(`
+    "{
+      ...Query
+    }
+
+    fragment Query on Query {
+      field
+    }
+    "
+  `);
+});
+
+it('interpolates strings', () => {
+  expect(
+    print(
+      gql`
+        query {
+          ${'field'}
+        }
+      `
+    )
+  ).toMatchInlineSnapshot(`
+    "{
+      field
+    }
+    "
+  `);
+});
diff --git a/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap b/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap
index f7319ebe..f8c68549 100644
--- a/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap
+++ b/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap
@@ -17,31 +17,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -49,20 +57,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -72,8 +85,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -83,8 +98,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -99,17 +116,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -120,6 +142,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
@@ -147,31 +185,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -179,20 +225,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -202,8 +253,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -213,8 +266,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -229,17 +284,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -250,6 +310,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
@@ -277,31 +353,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -309,20 +393,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -332,8 +421,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -343,8 +434,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -359,17 +452,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -380,6 +478,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
@@ -411,31 +525,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -443,20 +565,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -466,8 +593,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -477,8 +606,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -493,17 +624,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -514,6 +650,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
@@ -561,31 +713,39 @@ Object {
     "kind": "query",
     "operationName": "query",
     "query": Object {
+      "__key": 3044551916,
       "definitions": Array [
         Object {
           "directives": Array [],
           "kind": "OperationDefinition",
+          "loc": undefined,
           "name": Object {
             "kind": "Name",
+            "loc": undefined,
             "value": "getUser",
           },
           "operation": "query",
           "selectionSet": Object {
             "kind": "SelectionSet",
+            "loc": undefined,
             "selections": Array [
               Object {
                 "alias": undefined,
                 "arguments": Array [
                   Object {
                     "kind": "Argument",
+                    "loc": undefined,
                     "name": Object {
                       "kind": "Name",
+                      "loc": undefined,
                       "value": "name",
                     },
                     "value": Object {
                       "kind": "Variable",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "name",
                       },
                     },
@@ -593,20 +753,25 @@ Object {
                 ],
                 "directives": Array [],
                 "kind": "Field",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "user",
                 },
                 "selectionSet": Object {
                   "kind": "SelectionSet",
+                  "loc": undefined,
                   "selections": Array [
                     Object {
                       "alias": undefined,
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "id",
                       },
                       "selectionSet": undefined,
@@ -616,8 +781,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "firstName",
                       },
                       "selectionSet": undefined,
@@ -627,8 +794,10 @@ Object {
                       "arguments": Array [],
                       "directives": Array [],
                       "kind": "Field",
+                      "loc": undefined,
                       "name": Object {
                         "kind": "Name",
+                        "loc": undefined,
                         "value": "lastName",
                       },
                       "selectionSet": undefined,
@@ -643,17 +812,22 @@ Object {
               "defaultValue": undefined,
               "directives": Array [],
               "kind": "VariableDefinition",
+              "loc": undefined,
               "type": Object {
                 "kind": "NamedType",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "String",
                 },
               },
               "variable": Object {
                 "kind": "Variable",
+                "loc": undefined,
                 "name": Object {
                   "kind": "Name",
+                  "loc": undefined,
                   "value": "name",
                 },
               },
@@ -664,6 +838,22 @@ Object {
       "kind": "Document",
       "loc": Object {
         "end": 124,
+        "source": Source {
+          "body": "
+    query getUser($name: String) {
+      user(name: $name) {
+        id
+        firstName
+        lastName
+      }
+    }
+  ",
+          "locationOffset": Object {
+            "column": 1,
+            "line": 1,
+          },
+          "name": "gql",
+        },
         "start": 0,
       },
     },
diff --git a/packages/core/src/test-utils/samples.ts b/packages/core/src/test-utils/samples.ts
index 947a7b7c..048938e3 100644
--- a/packages/core/src/test-utils/samples.ts
+++ b/packages/core/src/test-utils/samples.ts
@@ -1,4 +1,4 @@
-import gql from 'graphql-tag';
+import { gql } from '../gql';
 
 import {
   ExecutionResult,
diff --git a/packages/core/src/utils/request.test.ts b/packages/core/src/utils/request.test.ts
index 2e196c15..34400ad3 100644
--- a/packages/core/src/utils/request.test.ts
+++ b/packages/core/src/utils/request.test.ts
@@ -1,5 +1,5 @@
 import { parse, print } from 'graphql';
-import gql from 'graphql-tag';
+import { gql } from '../gql';
 import { createRequest } from './request';
 
 it('should hash identical queries identically', () => {
diff --git a/packages/preact-urql/src/hooks/useMutation.test.tsx b/packages/preact-urql/src/hooks/useMutation.test.tsx
index c04160ee..4010d49b 100644
--- a/packages/preact-urql/src/hooks/useMutation.test.tsx
+++ b/packages/preact-urql/src/hooks/useMutation.test.tsx
@@ -1,7 +1,7 @@
 import { FunctionalComponent as FC, h } from 'preact';
 import { render, cleanup, act } from '@testing-library/preact';
 import { print } from 'graphql';
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { useMutation } from './useMutation';
 import { fromValue, delay, pipe } from 'wonka';
 import { Provider } from '../context';
@@ -62,7 +62,7 @@ describe('useMutation', () => {
     const call = client.executeMutation.mock.calls[0][0];
     expect(state).toHaveProperty('fetching', true);
     expect(client.executeMutation).toBeCalledTimes(1);
-    expect(print(call.query)).toBe(print(gql([props.query])));
+    expect(print(call.query)).toBe(print(gql(props.query)));
     expect(call).toHaveProperty('variables', vars);
   });
 
diff --git a/packages/react-urql/src/hooks/useMutation.test.tsx b/packages/react-urql/src/hooks/useMutation.test.tsx
index 588a253d..1db70ac9 100644
--- a/packages/react-urql/src/hooks/useMutation.test.tsx
+++ b/packages/react-urql/src/hooks/useMutation.test.tsx
@@ -16,7 +16,7 @@ jest.mock('../context', () => {
 });
 
 import { print } from 'graphql';
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import React, { FC } from 'react';
 import renderer, { act } from 'react-test-renderer';
 
@@ -89,7 +89,7 @@ describe('on execute', () => {
     });
 
     const call = client.executeMutation.mock.calls[0][0];
-    expect(print(call.query)).toBe(print(gql([props.query])));
+    expect(print(call.query)).toBe(print(gql(props.query)));
   });
 
   it('calls executeMutation with variables', () => {
diff --git a/packages/react-urql/src/hooks/useRequest.test.ts b/packages/react-urql/src/hooks/useRequest.test.ts
index c69fd6d6..f9b88c6a 100644
--- a/packages/react-urql/src/hooks/useRequest.test.ts
+++ b/packages/react-urql/src/hooks/useRequest.test.ts
@@ -1,4 +1,4 @@
-import gql from 'graphql-tag';
+import { gql } from '@urql/core';
 import { renderHook } from '@testing-library/react-hooks';
 import { useRequest } from './useRequest';
 
diff --git a/packages/react-urql/src/test-utils/ssr.test.tsx b/packages/react-urql/src/test-utils/ssr.test.tsx
index 42746e76..b47f7e3b 100644
--- a/packages/react-urql/src/test-utils/ssr.test.tsx
+++ b/packages/react-urql/src/test-utils/ssr.test.tsx
@@ -1,9 +1,9 @@
 import React from 'react';
-import gql from 'graphql-tag';
 import prepass from 'react-ssr-prepass';
 import { never, publish, filter, delay, pipe, map } from 'wonka';
 
 import {
+  gql,
   createClient,
   Exchange,
   dedupExchange,