From a020b878e62d8587246c2cf6250282f0cb25a9e3 Mon Sep 17 00:00:00 2001 From: Jeff Escalante Date: Tue, 7 Mar 2017 00:39:26 -0500 Subject: [PATCH] graphql implementation --- README.md | 23 +++++++++++++++++++---- lib/index.js | 14 ++++++++++++++ test/fixtures/graphql/app.js | 0 test/fixtures/graphql/index.sgr | 1 + test/index.js | 24 ++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/graphql/app.js create mode 100644 test/fixtures/graphql/index.sgr diff --git a/README.md b/README.md index a895153..b6e641d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,14 @@ remote data -> static templates ## Why should you care? -Static is the best, but sometimes you need to fetch data from a remote source which makes things not so static. Spike Records is a little webpack plugin intended for use with [spike](https://github.com/static-dev/spike) which allows you to make locals pulled from a JSON file or url returning JSON available as static locals in your jade templates. +Static is the best, but sometimes you need to fetch data from a remote source which makes things not so static. Spike Records is a little webpack plugin intended for use with [spike](https://github.com/static-dev/spike) which allows you to make data pulled from a file or url available in your view templates. + +It can pull data from the following places: + +- A javascript object +- A file containing a javascript object or JSON +- A URL that returns JSON +- A [GraphQL](http://graphql.org) endpoint ## Installation @@ -34,9 +41,9 @@ module.exports = { The primary use case for spike-records is to inject local variables into your html templates, although technically it can be used for anything. In the example above, we use the [reshape-standard](https://github.com/reshape/standard) plugin pack to add variables (among other functionality) to your html. Spike's default template also uses `reshape-standard`. -In order to use the results from spike-records, you must pass it an object, which it will put the resolved data on, using the `addDataTo` key. This plugin runs very early in spike's compile process, so by the time templates are being compiled, the object will have all the data necessary on it. If you are using the data with other plugins, ensure that spike-records is the first plugin in the array. +In order to use the results from spike-records, you must pass it an object, which it will put the resolved data on, using the `addDataTo` key. This plugin runs early in spike's compile process, so by the time templates are being compiled, the object will have all the data necessary on it. If you are using the data with other plugins, ensure that spike-records is the first plugin in the array. -I know this is an unusual pattern for a javascript library, but the way it works is very effective in this particular system, and affords a lot of flexibility and power. +I know this is an unusual pattern for a javascript library, but the way it works is quite effective in this particular system, and affords a lot of flexibility and power. The records plugin accepts an object, and each key in the object (other than `addDataTo`) should contain another object as it's value, with either a `file`, `url`, or `data` property. For example: @@ -47,7 +54,15 @@ new Records({ addDataTo: locals, one: { file: 'data.json' }, two: { url: 'http://api.carrotcreative.com/staff' }, - three: { data: { foo: 'bar' } } + three: { data: { foo: 'bar' } }, + four: { + graphql: { + url: 'http://localhost:1234', + query: 'query { allPosts { title } }', + variables: 'xxx', // optional + headers: { authorization: 'Bearer xxx' } // optional + } + } }) ``` diff --git a/lib/index.js b/lib/index.js index c51e51b..d559a6b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -63,6 +63,7 @@ function run (compiler, compilation, done) { for (const k in this.opts) { if (this.opts[k].data) { tasks[k] = renderData(this.opts[k]) } if (this.opts[k].url) { tasks[k] = renderUrl(this.opts[k]) } + if (this.opts[k].graphql) { tasks[k] = renderGraphql(this.opts[k]) } if (this.opts[k].file) { tasks[k] = renderFile(compiler.options.context, this.opts[k]) } @@ -109,6 +110,19 @@ function renderUrl (obj) { return rest(obj.url).then((res) => { return JSON.parse(res.entity) }) } +function renderGraphql (obj) { + const headers = Object.assign({ 'Content-Type': 'application/json' }, obj.graphql.headers) + + return rest({ + path: obj.graphql.url, + entity: JSON.stringify({ + query: obj.graphql.query, + variables: obj.graphql.variables + }), + headers + }).then((res) => { return JSON.parse(res.entity) }) +} + /** * If the user provided a transform function for a given data source, run the * function and return the transformed data. Otherwise, return the data as it diff --git a/test/fixtures/graphql/app.js b/test/fixtures/graphql/app.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/graphql/index.sgr b/test/fixtures/graphql/index.sgr new file mode 100644 index 0000000..853d419 --- /dev/null +++ b/test/fixtures/graphql/index.sgr @@ -0,0 +1 @@ +p {{ test.data.allCakes[0].image.url }} diff --git a/test/index.js b/test/index.js index 426c07d..0043e07 100644 --- a/test/index.js +++ b/test/index.js @@ -47,6 +47,30 @@ test('loads a url correctly', (t) => { }) }) +test('loads a graphql endpoint correctly', (t) => { + const locals = {} + return compileAndCheck({ + fixture: 'graphql', + locals: locals, + config: { + addDataTo: locals, + test: { + graphql: { + url: 'https://api.graphcms.com/simple/v1/cizc7giha88sc0159irzllhl2', + query: '{ allCakes { image { url } } }', + headers: { + 'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0ODg4NjM5ODMsImNsaWVudElkIjoiY2l2Z29zNmNqMDE5MjAxODRucDAxZGRkMiIsInByb2plY3RJZCI6ImNpemM3Z2loYTg4c2MwMTU5aXJ6bGxobDIiLCJwZXJtYW5lbnRBdXRoVG9rZW5JZCI6ImNpenozNjlxcm0zY3QwMTMzNGphY3lpYTYifQ.DVofROGTyUfpbot1HDXURT1uKLzTsJ7ly_p61ITAqY4' + } + } + } + }, + verify: (_, publicPath, cb) => { + const out = fs.readFileSync(path.join(publicPath, 'index.html'), 'utf8') + t.regex(out, /media\.graphcms\.com/) + } + }) +}) + test('transform option works', (t) => { const locals = {} return compileAndCheck({