├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── src └── index.js └── test ├── fixtures ├── example │ ├── actual.js │ └── expected.js ├── fields │ ├── actual.js │ └── expected.js ├── fragments │ ├── actual.js │ └── expected.js ├── literals │ ├── actual.js │ └── expected.js ├── references │ ├── actual.js │ └── expected.js └── variables │ ├── actual.js │ └── expected.js ├── index.js └── mocha.opts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "loose": true, 4 | "blacklist": [ 5 | "es6.tailCall" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | 4 | "env": { 5 | "es6": true, 6 | "browser": true, 7 | "node": true, 8 | "mocha": true 9 | }, 10 | 11 | "rules": { 12 | "block-scoped-var": 0, 13 | "brace-style": [2, "1tbs", {"allowSingleLine": true}], 14 | "camelcase": 0, 15 | "comma-dangle": [2, "always-multiline"], 16 | "comma-style": [2, "last"], 17 | "consistent-this": [2, "self"], 18 | "curly": 0, 19 | "indent": [2, 2], 20 | "quotes": [2, "single", "avoid-escape"], 21 | "no-multiple-empty-lines": [2, {"max": 1}], 22 | "no-self-compare": 2, 23 | "no-underscore-dangle": 0, 24 | "no-unused-vars": [1, {"vars": "all", "args": "none"}], 25 | "no-use-before-define": 0, 26 | "no-var": 2, 27 | "semi": [2, "never"], 28 | "space-after-keywords": [2, "always"], 29 | "space-before-blocks": [2, "always"], 30 | "space-before-function-parentheses": [2, "never"], 31 | "space-in-parens": [2, "never"], 32 | "spaced-line-comment": [2, "always"], 33 | "strict": 0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0 4 | 5 | * Upgrade to latest babel plugin API 6 | * Upgrade `graphql-parser` to `2.0.0` 7 | 8 | ## 1.1.0 9 | 10 | * Make `GraphQL` import unnecessary 11 | 12 | ## 1.0.2 13 | 14 | * NPM 3 compatibility 15 | 16 | ## 1.0.1 17 | 18 | * Move `babel-core` to `peerDependencies` 19 | 20 | ## 1.0.0 21 | 22 | * Initial release 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Florent Cailhol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-graphql 2 | 3 | > Babel plugin that compile GraphQL tagged template strings. 4 | 5 | _Issues related to GraphQL parsing should be reporter on `graphql-parser` [issue-tracker][graphql-parser-gh]._ 6 | 7 | ## Install 8 | 9 | ```sh 10 | npm install --save-dev babel-plugin-graphql 11 | ``` 12 | 13 | ## Usage 14 | 15 | Run: 16 | 17 | ```sh 18 | babel --plugins graphql script.js 19 | ``` 20 | 21 | Or add the plugin to your `.babelrc` configuration: 22 | 23 | ```json 24 | { 25 | "plugins": [ "graphql" ] 26 | } 27 | ``` 28 | 29 | __Note:__ Due to current API limitations you need to enable `es7.objectRestSpread` transformer or _stage 1_ transformers. 30 | 31 | ## Example 32 | 33 | The plugin will compile the following code: 34 | 35 | ```js 36 | const IMAGE_WIDTH = 80 37 | const IMAGE_HEIGHT = 80 38 | 39 | const PostFragment = graphql` 40 | { 41 | post { 42 | title, 43 | published_at 44 | } 45 | } 46 | ` 47 | 48 | const UserQuery = graphql` 49 | { 50 | user(id: ) { 51 | nickname, 52 | avatar(width: ${IMAGE_WIDTH}, height: ${IMAGE_HEIGHT}) { 53 | url 54 | }, 55 | posts(first: ) { 56 | count, 57 | edges { 58 | node { 59 | ${ PostFragment() } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | ` 66 | ``` 67 | 68 | into: 69 | 70 | ```js 71 | var IMAGE_WIDTH = 80; 72 | var IMAGE_HEIGHT = 80; 73 | 74 | var PostFragment = function PostFragment(params) { 75 | return { 76 | fields: { 77 | post: { 78 | fields: { 79 | title: {}, 80 | published_at: {} 81 | } 82 | } 83 | } 84 | }; 85 | }; 86 | 87 | var UserQuery = function UserQuery(params) { 88 | return { 89 | fields: { 90 | user: { 91 | params: { 92 | id: params.id 93 | }, 94 | fields: { 95 | nickname: {}, 96 | avatar: { 97 | params: { 98 | width: IMAGE_WIDTH, 99 | height: IMAGE_HEIGHT 100 | }, 101 | fields: { 102 | url: {} 103 | } 104 | }, 105 | posts: { 106 | params: { 107 | first: params.count 108 | }, 109 | fields: { 110 | count: {}, 111 | edges: { 112 | fields: { 113 | node: { 114 | fields: _extends({}, PostFragment().fields) 115 | } 116 | } 117 | } 118 | } 119 | } 120 | } 121 | } 122 | } 123 | }; 124 | }; 125 | ``` 126 | 127 | [graphql-parser-gh]: https://github.com/ooflorent/graphql-parser/issues 128 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-graphql", 3 | "version": "2.0.2", 4 | "description": "Babel plugin that compiles GraphQL tagged template strings", 5 | "author": "Florent Cailhol ", 6 | "license": "MIT", 7 | "repository": "ooflorent/babel-plugin-graphql", 8 | "keywords": [ 9 | "babel-plugin", 10 | "graphql" 11 | ], 12 | "main": "lib/index.js", 13 | "files": [ 14 | "lib" 15 | ], 16 | "dependencies": { 17 | "graphql-parser": "^2.0.0", 18 | "lodash": "^3.9.0" 19 | }, 20 | "devDependencies": { 21 | "babel": "^5.4.5", 22 | "babel-eslint": "^3.1.5", 23 | "eslint": "^0.21.1", 24 | "mocha": "^2.2.5" 25 | }, 26 | "scripts": { 27 | "clean": "rm -rf lib", 28 | "build": "babel src --out-dir lib --copy-files", 29 | "watch": "npm run build -- --watch", 30 | "lint": "eslint src/", 31 | "test": "mocha", 32 | "prepublish": "npm run clean && npm run build" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { parse, traverse } from 'graphql-parser' 2 | 3 | export default function build(babel) { 4 | const { types: t, Transformer } = babel 5 | 6 | class GraphQLVisitor { 7 | constructor(refs) { 8 | this.refs = refs 9 | } 10 | 11 | Query(node) { 12 | const props = [] 13 | const query = t.objectExpression(props) 14 | 15 | if (node.fields.length > 0) { 16 | props.push(compileFields(node.fields)) 17 | } 18 | 19 | return query 20 | } 21 | 22 | Field(node) { 23 | const props = [] 24 | 25 | if (node.alias) { 26 | props.push(t.property('init', t.identifier('alias'), t.valueToNode(node.alias))) 27 | } 28 | 29 | if (node.params.length > 0) { 30 | props.push(t.property('init', t.identifier('params'), t.objectExpression(node.params))) 31 | } 32 | 33 | if (node.fields.length > 0) { 34 | props.push(compileFields(node.fields)) 35 | } 36 | 37 | return t.property('init', t.identifier(node.name), t.objectExpression(props)) 38 | } 39 | 40 | Argument(node) { 41 | return t.property('init', t.identifier(node.name), node.value) 42 | } 43 | 44 | Literal(node) { 45 | return t.valueToNode(node.value) 46 | } 47 | 48 | Variable(node) { 49 | return t.memberExpression(t.identifier('params'), t.identifier(node.name)) 50 | } 51 | 52 | Reference(node) { 53 | return this.refs[node.name] 54 | } 55 | } 56 | 57 | function compileFields(fields) { 58 | for (let i = 0; i < fields.length; i++) { 59 | const field = fields[i] 60 | if (t.isCallExpression(field)) { 61 | fields[i] = t.spreadProperty(t.memberExpression(field, t.identifier('fields'))) 62 | } 63 | } 64 | 65 | return t.property('init', t.identifier('fields'), t.objectExpression(fields)) 66 | } 67 | 68 | function compile(node) { 69 | let source = '' 70 | for (let i = 0; i < node.quasis.length; i++) { 71 | if (i > 0) source += '&' + (i - 1) 72 | source += node.quasis[i].value.raw 73 | } 74 | 75 | return t.functionExpression(null, [t.identifier('params')], t.blockStatement([ 76 | t.returnStatement(traverse(parse(source), new GraphQLVisitor(node.expressions))), 77 | ])) 78 | } 79 | 80 | return new Transformer('graphql', { 81 | TaggedTemplateExpression: { 82 | enter(node) { 83 | if (t.isIdentifier(node.tag, {name: 'graphql'})) { 84 | return compile(node.quasi) 85 | } 86 | } 87 | } 88 | }) 89 | } 90 | -------------------------------------------------------------------------------- /test/fixtures/example/actual.js: -------------------------------------------------------------------------------- 1 | const IMAGE_WIDTH = 80 2 | const IMAGE_HEIGHT = 80 3 | 4 | const PostFragment = graphql` 5 | { 6 | post { 7 | title, 8 | published_at 9 | } 10 | } 11 | ` 12 | 13 | const UserQuery = graphql` 14 | { 15 | user(id: ) { 16 | nickname, 17 | avatar(width: ${IMAGE_WIDTH}, height: ${IMAGE_HEIGHT}) { 18 | url 19 | }, 20 | posts(first: ) { 21 | count, 22 | edges { 23 | node { 24 | ${ PostFragment() } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | ` 31 | -------------------------------------------------------------------------------- /test/fixtures/example/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 4 | 5 | var IMAGE_WIDTH = 80; 6 | var IMAGE_HEIGHT = 80; 7 | 8 | var PostFragment = function PostFragment(params) { 9 | return { 10 | fields: { 11 | post: { 12 | fields: { 13 | title: {}, 14 | published_at: {} 15 | } 16 | } 17 | } 18 | }; 19 | }; 20 | 21 | var UserQuery = function UserQuery(params) { 22 | return { 23 | fields: { 24 | user: { 25 | params: { 26 | id: params.id 27 | }, 28 | fields: { 29 | nickname: {}, 30 | avatar: { 31 | params: { 32 | width: IMAGE_WIDTH, 33 | height: IMAGE_HEIGHT 34 | }, 35 | fields: { 36 | url: {} 37 | } 38 | }, 39 | posts: { 40 | params: { 41 | first: params.count 42 | }, 43 | fields: { 44 | count: {}, 45 | edges: { 46 | fields: { 47 | node: { 48 | fields: _extends({}, PostFragment().fields) 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | }; 58 | }; -------------------------------------------------------------------------------- /test/fixtures/fields/actual.js: -------------------------------------------------------------------------------- 1 | var query = graphql` 2 | { 3 | a, 4 | b { 5 | c, 6 | d { 7 | e 8 | }, 9 | f, 10 | g {} 11 | } 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /test/fixtures/fields/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var query = function query(params) { 4 | return { 5 | fields: { 6 | a: {}, 7 | b: { 8 | fields: { 9 | c: {}, 10 | d: { 11 | fields: { 12 | e: {} 13 | } 14 | }, 15 | f: {}, 16 | g: {} 17 | } 18 | } 19 | } 20 | }; 21 | }; -------------------------------------------------------------------------------- /test/fixtures/fragments/actual.js: -------------------------------------------------------------------------------- 1 | var fragmentA = graphql`{ a, b }` 2 | var fragmentB = graphql`{ c, d }` 3 | var query = graphql`{ ${ fragmentA() }, ${ fragmentB() }, e, f }` 4 | -------------------------------------------------------------------------------- /test/fixtures/fragments/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 4 | 5 | var fragmentA = function fragmentA(params) { 6 | return { 7 | fields: { 8 | a: {}, 9 | b: {} 10 | } 11 | }; 12 | }; 13 | var fragmentB = function fragmentB(params) { 14 | return { 15 | fields: { 16 | c: {}, 17 | d: {} 18 | } 19 | }; 20 | }; 21 | var query = function query(params) { 22 | return { 23 | fields: _extends({}, fragmentA().fields, fragmentB().fields, { 24 | e: {}, 25 | f: {} 26 | }) 27 | }; 28 | }; -------------------------------------------------------------------------------- /test/fixtures/literals/actual.js: -------------------------------------------------------------------------------- 1 | var query = graphql` 2 | { 3 | a(b: 12, c: null, d: true, e: "f") 4 | } 5 | ` 6 | -------------------------------------------------------------------------------- /test/fixtures/literals/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var query = function query(params) { 4 | return { 5 | fields: { 6 | a: { 7 | params: { 8 | b: 12, 9 | c: null, 10 | d: true, 11 | e: "f" 12 | } 13 | } 14 | } 15 | }; 16 | }; -------------------------------------------------------------------------------- /test/fixtures/references/actual.js: -------------------------------------------------------------------------------- 1 | var ref = "bar" 2 | var query = graphql` 3 | { 4 | a(b: ${ 0 }, c: ${ "foo" }, d: ${ ref }) 5 | } 6 | ` 7 | -------------------------------------------------------------------------------- /test/fixtures/references/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var ref = "bar"; 4 | var query = function query(params) { 5 | return { 6 | fields: { 7 | a: { 8 | params: { 9 | b: 0, 10 | c: "foo", 11 | d: ref 12 | } 13 | } 14 | } 15 | }; 16 | }; -------------------------------------------------------------------------------- /test/fixtures/variables/actual.js: -------------------------------------------------------------------------------- 1 | var query = graphql` 2 | { 3 | a(b: <_0>) { 4 | d(e: <_1>, g: <_2>) 5 | } 6 | } 7 | ` 8 | -------------------------------------------------------------------------------- /test/fixtures/variables/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var query = function query(params) { 4 | return { 5 | fields: { 6 | a: { 7 | params: { 8 | b: params._0 9 | }, 10 | fields: { 11 | d: { 12 | params: { 13 | e: params._1, 14 | g: params._2 15 | } 16 | } 17 | } 18 | } 19 | } 20 | }; 21 | }; -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import Plugin from '../src' 2 | import assert from 'assert' 3 | import fs from 'fs' 4 | import { transform } from 'babel' 5 | 6 | const opts = { 7 | optional: [ 'es7.objectRestSpread' ], 8 | plugins: [ Plugin ], 9 | } 10 | 11 | function readFile(filename) { 12 | return fs.readFileSync(`${ __dirname }/fixtures/${ filename }`, 'utf8') 13 | } 14 | 15 | function run(testName) { 16 | const actualCode = readFile(`${ testName }/actual.js`) 17 | const expectedCode = readFile(`${ testName }/expected.js`) 18 | 19 | it(`compiles ${ testName }`, () => { 20 | const result = transform(actualCode, opts) 21 | assert.equal(result.code.trim(), expectedCode) 22 | }) 23 | } 24 | 25 | describe('graphql', () => { 26 | // Spec 27 | run('fields') 28 | run('literals') 29 | run('variables') 30 | run('references') 31 | run('fragments') 32 | 33 | // Mics 34 | run('example') 35 | }) 36 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel/register 2 | --reporter spec 3 | --------------------------------------------------------------------------------