From 75709be6623951a3ec6e9d652c05807fafe2b6ae Mon Sep 17 00:00:00 2001 From: Leonardo Andres Garcia Crespo Date: Mon, 18 Sep 2017 11:11:25 -0300 Subject: [PATCH 1/3] Add fragment interpolation support --- src/index.js | 36 +++- .../actual.js | 31 ++++ .../expected.js | 170 ++++++++++++++++++ .../options.json | 7 + 4 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/actual.js create mode 100644 test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/expected.js create mode 100644 test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/options.json diff --git a/src/index.js b/src/index.js index 5f8a9e3..0d148a5 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,10 @@ import { isIdentifier, - TemplateLiteral + isMemberExpression, + memberExpression, + callExpression, + identifier } from 'babel-types'; import parse from 'babel-literal-to-ast'; import gql from 'graphql-tag'; @@ -11,11 +14,19 @@ import createDebug from 'debug'; const debug = createDebug('babel-plugin-graphql-tag'); export default () => { - const compile = (node: TemplateLiteral) => { - const source = node.quasis.reduce((head, quasi) => { + const compile = (path: Object) => { + const source = path.node.quasis.reduce((head, quasi) => { return head + quasi.value.raw; }, ''); + const expressions = path.get('expressions'); + + expressions.forEach((expr) => { + if (!isIdentifier(expr) && !isMemberExpression(expr)) { + throw expr.buildCodeFrameError('Only identifiers or member expressions are allowed by this plugin as an interpolation in a graphql template literal.'); + } + }); + debug('compiling a GraphQL query', source); const queryDocument = gql(source); @@ -28,6 +39,23 @@ export default () => { const body = parse(queryDocument); + if (expressions.length) { + const definitionsProperty = body.properties.find((property) => { + return property.key.value === 'definitions'; + }); + + const definitionsArray = definitionsProperty.value; + + const extraDefinitions = expressions.map((expr) => { + return memberExpression(expr.node, identifier('definitions')); + }); + + definitionsProperty.value = callExpression( + memberExpression(definitionsArray, identifier('concat')), + extraDefinitions + ); + } + debug('created a static representation', body); return body; @@ -47,7 +75,7 @@ export default () => { try { debug('quasi', path.node.quasi); - const body = compile(path.node.quasi); + const body = compile(path.get('quasi')); path.replaceWith(body); } catch (error) { diff --git a/test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/actual.js b/test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/actual.js new file mode 100644 index 0000000..11f7af9 --- /dev/null +++ b/test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/actual.js @@ -0,0 +1,31 @@ +import gql from 'graphql-tag'; + +const bar = gql` + fragment barFragment on Foo { + field1 + field2 + } +`; + +const baz = { + fragments: { + foo: gql` + fragment bazFragment on Foo { + field2 + field3 + } + ` + } +}; + +const foo = gql` + query foo { + foo { + ...barFragment + ...bazFragment + } + } + + ${bar} + ${baz.fragments.foo} +`; diff --git a/test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/expected.js b/test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/expected.js new file mode 100644 index 0000000..57e172f --- /dev/null +++ b/test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/expected.js @@ -0,0 +1,170 @@ +const bar = { + 'kind': 'Document', + 'definitions': [{ + 'kind': 'FragmentDefinition', + 'name': { + 'kind': 'Name', + 'value': 'barFragment' + }, + 'typeCondition': { + 'kind': 'NamedType', + 'name': { + 'kind': 'Name', + 'value': 'Foo' + } + }, + 'directives': [], + 'selectionSet': { + 'kind': 'SelectionSet', + 'selections': [{ + 'kind': 'Field', + 'alias': null, + 'name': { + 'kind': 'Name', + 'value': 'field1' + }, + 'arguments': [], + 'directives': [], + 'selectionSet': null + }, { + 'kind': 'Field', + 'alias': null, + 'name': { + 'kind': 'Name', + 'value': 'field2' + }, + 'arguments': [], + 'directives': [], + 'selectionSet': null + }] + } + }], + 'loc': { + 'start': 0, + 'end': 59, + 'source': { + 'body': '\n fragment barFragment on Foo {\n field1\n field2\n }\n', + 'name': 'GraphQL request', + 'locationOffset': { + 'line': 1, + 'column': 1 + } + } + } +}; + +const baz = { + fragments: { + foo: { + 'kind': 'Document', + 'definitions': [{ + 'kind': 'FragmentDefinition', + 'name': { + 'kind': 'Name', + 'value': 'bazFragment' + }, + 'typeCondition': { + 'kind': 'NamedType', + 'name': { + 'kind': 'Name', + 'value': 'Foo' + } + }, + 'directives': [], + 'selectionSet': { + 'kind': 'SelectionSet', + 'selections': [{ + 'kind': 'Field', + 'alias': null, + 'name': { + 'kind': 'Name', + 'value': 'field2' + }, + 'arguments': [], + 'directives': [], + 'selectionSet': null + }, { + 'kind': 'Field', + 'alias': null, + 'name': { + 'kind': 'Name', + 'value': 'field3' + }, + 'arguments': [], + 'directives': [], + 'selectionSet': null + }] + } + }], + 'loc': { + 'start': 0, + 'end': 79, + 'source': { + 'body': '\n fragment bazFragment on Foo {\n field2\n field3\n }\n ', + 'name': 'GraphQL request', + 'locationOffset': { + 'line': 1, + 'column': 1 + } + } + } + } + } +}; + +const foo = { + 'kind': 'Document', + 'definitions': [{ + 'kind': 'OperationDefinition', + 'operation': 'query', + 'name': { + 'kind': 'Name', + 'value': 'foo' + }, + 'variableDefinitions': [], + 'directives': [], + 'selectionSet': { + 'kind': 'SelectionSet', + 'selections': [{ + 'kind': 'Field', + 'alias': null, + 'name': { + 'kind': 'Name', + 'value': 'foo' + }, + 'arguments': [], + 'directives': [], + 'selectionSet': { + 'kind': 'SelectionSet', + 'selections': [{ + 'kind': 'FragmentSpread', + 'name': { + 'kind': 'Name', + 'value': 'barFragment' + }, + 'directives': [] + }, { + 'kind': 'FragmentSpread', + 'name': { + 'kind': 'Name', + 'value': 'bazFragment' + }, + 'directives': [] + }] + } + }] + } + }].concat(bar.definitions, baz.fragments.foo.definitions), + 'loc': { + 'start': 0, + 'end': 84, + 'source': { + 'body': '\n query foo {\n foo {\n ...barFragment\n ...bazFragment\n }\n }\n\n \n \n', + 'name': 'GraphQL request', + 'locationOffset': { + 'line': 1, + 'column': 1 + } + } + } +}; diff --git a/test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/options.json b/test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/options.json new file mode 100644 index 0000000..5acbd80 --- /dev/null +++ b/test/fixtures/graphql-tag/converts inline gql tag with fragment interpolation/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + [ + "../../../../src" + ] + ] +} From cc4be9fe0de1d4d9d2697ba21871de0ec977eda6 Mon Sep 17 00:00:00 2001 From: Leonardo Andres Garcia Crespo Date: Mon, 18 Sep 2017 15:31:27 -0300 Subject: [PATCH 2/3] Add example with fragments in README --- README.md | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/README.md b/README.md index 9c642e6..723b208 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,137 @@ const foo = { }; ``` + +### With fragments + +Input: + +```js +import gql from 'graphql-tag'; + +const bar = gql` + fragment barFragment on Foo { + field1 + field2 + } +`; + +const foo = gql` + query foo { + foo { + ...barFragment + } + } + + ${bar} +`; +``` + +Output: + +```js +const bar = { + 'kind': 'Document', + 'definitions': [{ + 'kind': 'FragmentDefinition', + 'name': { + 'kind': 'Name', + 'value': 'barFragment' + }, + 'typeCondition': { + 'kind': 'NamedType', + 'name': { + 'kind': 'Name', + 'value': 'Foo' + } + }, + 'directives': [], + 'selectionSet': { + 'kind': 'SelectionSet', + 'selections': [{ + 'kind': 'Field', + 'alias': null, + 'name': { + 'kind': 'Name', + 'value': 'field1' + }, + 'arguments': [], + 'directives': [], + 'selectionSet': null + }, { + 'kind': 'Field', + 'alias': null, + 'name': { + 'kind': 'Name', + 'value': 'field2' + }, + 'arguments': [], + 'directives': [], + 'selectionSet': null + }] + } + }], + 'loc': { + 'start': 0, + 'end': 59, + 'source': { + 'body': '\n fragment barFragment on Foo {\n field1\n field2\n }\n', + 'name': 'GraphQL request', + 'locationOffset': { + 'line': 1, + 'column': 1 + } + } + } +}; + +const foo = { + 'kind': 'Document', + 'definitions': [{ + 'kind': 'OperationDefinition', + 'operation': 'query', + 'name': { + 'kind': 'Name', + 'value': 'foo' + }, + 'variableDefinitions': [], + 'directives': [], + 'selectionSet': { + 'kind': 'SelectionSet', + 'selections': [{ + 'kind': 'Field', + 'alias': null, + 'name': { + 'kind': 'Name', + 'value': 'foo' + }, + 'arguments': [], + 'directives': [], + 'selectionSet': { + 'kind': 'SelectionSet', + 'selections': [{ + 'kind': 'FragmentSpread', + 'name': { + 'kind': 'Name', + 'value': 'barFragment' + }, + 'directives': [] + }] + } + }] + } + }].concat(bar.definitions), + 'loc': { + 'start': 0, + 'end': 60, + 'source': { + 'body': '\n query foo {\n foo {\n ...barFragment\n }\n }\n\n \n', + 'name': 'GraphQL request', + 'locationOffset': { + 'line': 1, + 'column': 1 + } + } + } +}; +``` From 1e0ba3c65b679944dc5bbfa9bb19e2b66f8f3e44 Mon Sep 17 00:00:00 2001 From: Gajus Kuizinas Date: Mon, 18 Sep 2017 19:39:01 +0100 Subject: [PATCH 3/3] docs: improve wording --- README.md | 118 ++++-------------------------------------------------- 1 file changed, 8 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 723b208..57a069e 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,14 @@ const foo = { ``` -### With fragments +### Using fragments -Input: +Using GraphQL [fragments](http://graphql.org/learn/queries/#fragments) requires to: + +1. Define a fragment using `graphql-tag`. +2. Append the referenced fragment as a variable to the end of the GraphQL query. + +Example: ```js import gql from 'graphql-tag'; @@ -96,113 +101,6 @@ const foo = gql` ${bar} `; -``` - -Output: -```js -const bar = { - 'kind': 'Document', - 'definitions': [{ - 'kind': 'FragmentDefinition', - 'name': { - 'kind': 'Name', - 'value': 'barFragment' - }, - 'typeCondition': { - 'kind': 'NamedType', - 'name': { - 'kind': 'Name', - 'value': 'Foo' - } - }, - 'directives': [], - 'selectionSet': { - 'kind': 'SelectionSet', - 'selections': [{ - 'kind': 'Field', - 'alias': null, - 'name': { - 'kind': 'Name', - 'value': 'field1' - }, - 'arguments': [], - 'directives': [], - 'selectionSet': null - }, { - 'kind': 'Field', - 'alias': null, - 'name': { - 'kind': 'Name', - 'value': 'field2' - }, - 'arguments': [], - 'directives': [], - 'selectionSet': null - }] - } - }], - 'loc': { - 'start': 0, - 'end': 59, - 'source': { - 'body': '\n fragment barFragment on Foo {\n field1\n field2\n }\n', - 'name': 'GraphQL request', - 'locationOffset': { - 'line': 1, - 'column': 1 - } - } - } -}; - -const foo = { - 'kind': 'Document', - 'definitions': [{ - 'kind': 'OperationDefinition', - 'operation': 'query', - 'name': { - 'kind': 'Name', - 'value': 'foo' - }, - 'variableDefinitions': [], - 'directives': [], - 'selectionSet': { - 'kind': 'SelectionSet', - 'selections': [{ - 'kind': 'Field', - 'alias': null, - 'name': { - 'kind': 'Name', - 'value': 'foo' - }, - 'arguments': [], - 'directives': [], - 'selectionSet': { - 'kind': 'SelectionSet', - 'selections': [{ - 'kind': 'FragmentSpread', - 'name': { - 'kind': 'Name', - 'value': 'barFragment' - }, - 'directives': [] - }] - } - }] - } - }].concat(bar.definitions), - 'loc': { - 'start': 0, - 'end': 60, - 'source': { - 'body': '\n query foo {\n foo {\n ...barFragment\n }\n }\n\n \n', - 'name': 'GraphQL request', - 'locationOffset': { - 'line': 1, - 'column': 1 - } - } - } -}; ``` +