Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add out-of-the-box support for query IDs and persisted queries (automatic + manual) #178

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,34 @@ console.log(query);
// ...
```

graphql-tag/loader also supports hashing of queries, to provide support for persisted queries out-of-the-box.

```js
loaders: [
{
test: /\.(graphql|gql)$/,
exclude: /node_modules/,
use: [
{
loader: 'graphql-tag/loader',
options: {
// Attach queryId to document (default: true)
hashQueries: true,

// Generate a map file for the server (if you don't want to use auto persisted queries).
// See https://dev-blog.apollodata.com/persisted-graphql-queries-with-apollo-client-119fd7e6bba5
// and https://www.apollographql.com/docs/engine/auto-persisted-queries.html
generateHashMap: true,

// If you want to seed the queryMap with an existing query map file (default: undefined)
queryMapPath: '',
},
},
],
}
]
```

Testing environments that don't support Webpack require additional configuration. For [Jest](https://facebook.github.io/jest/) use [jest-transform-graphql](https://github.com/remind101/jest-transform-graphql).

#### Support for multiple operations
Expand Down
98 changes: 91 additions & 7 deletions loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

const os = require('os');
const gql = require('./src');
const crypto = require('crypto')
const fs = require('fs');
const { ExtractGQL } = require('persistgraphql/lib/src/ExtractGQL');
const queryTransformers = require('persistgraphql/lib/src/queryTransformers');

let queryMap = {};

// Takes `source` (the source GraphQL query string)
// and `doc` (the parsed GraphQL document) and tacks on
Expand Down Expand Up @@ -39,9 +45,59 @@ function expandImports(source, doc) {
return outputCode;
}

function seedQueryMap(existingQueryMapPath) {
console.info('Seeding query map...');

try {
fs.statSync(existingQueryMapPath);

console.info(`Query map path found at: ${existingQueryMapPath}`);

try {
const existingQueryMap = JSON.parse(fs.readFileSync(existingQueryMapPath, 'utf8'));

// Seed queryMap with existingQueryMap
Object.assign(queryMap, existingQueryMap, queryMap);
} catch (err) {
console.error(err);
}
}
catch (err) {
console.error(`Query map path NOT found at: ${existingQueryMapPath}`);
}
}

function addQueryId(queries, index) {
const query = queries[index];

const sha256 = crypto.createHash('sha256');
const queryId = sha256.update(JSON.stringify(query)).digest('hex');

// Save queryId to query mapping
queryMap[queryId] = query;

return queryId;
}

module.exports = function(source) {
const {hashQueries = true, existingQueryMapPath, generateHashMap = false} = this.query;

// If there is an existing query map file that should be the seed, then load it now
// (but only if it hasn't already been loaded).
if (hashQueries && existingQueryMapPath && Object.keys(queryMap).length === 0) {
seedQueryMap(existingQueryMapPath);
}

this.cacheable();
const doc = gql`${source}`;

let queries = [];
if (hashQueries) {
queries = Object.keys(new ExtractGQL({
queryTransformers: [queryTransformers.addTypenameTransformer].filter(Boolean)
}).createOutputMapFromString(source));
}

let headerCode = `
var doc = ${JSON.stringify(doc)};
doc.loc.source = ${JSON.stringify(doc.loc.source)};
Expand All @@ -62,7 +118,7 @@ module.exports = function(source) {

if (operationCount < 1) {
outputCode += `
module.exports = doc;
module.exports = doc;
`
} else {
outputCode += `
Expand Down Expand Up @@ -113,11 +169,12 @@ module.exports = function(source) {
});
}

function oneQuery(doc, operationName) {
function oneQuery(doc, operationName, queryId) {
// Copy the DocumentNode, but clear out the definitions
var newDoc = {
kind: doc.kind,
definitions: [findOperation(doc, operationName)]
definitions: [findOperation(doc, operationName)],
${hashQueries ? 'queryId: queryId' : ''}
};
if (doc.hasOwnProperty("loc")) {
newDoc.loc = doc.loc;
Expand Down Expand Up @@ -154,21 +211,38 @@ module.exports = function(source) {
}

module.exports = doc;
`
`;

for (let i = 0; i < doc.definitions.length; i++) {
const op = doc.definitions[i];

for (const op of doc.definitions) {
if (op.kind === "OperationDefinition") {
let queryId;

if (!op.name) {
if (operationCount > 1) {
throw "Query/mutation names are required for a document with multiple definitions";
} else {
if (hashQueries) {
queryId = addQueryId(queries, 0);

outputCode += `
${hashQueries ? `doc.queryId = '${queryId}';` : ''}
`;
}

continue;
}
}

if (hashQueries) {
queryId = addQueryId(queries, i);
}

const opName = op.name.value;

outputCode += `
module.exports["${opName}"] = oneQuery(doc, "${opName}");
module.exports["${opName}"] = oneQuery(doc, "${opName}", "${queryId}");
`
}
}
Expand All @@ -177,5 +251,15 @@ module.exports = function(source) {
const importOutputCode = expandImports(source, doc);
const allCode = headerCode + os.EOL + importOutputCode + os.EOL + outputCode + os.EOL;

return allCode;
if (hashQueries && generateHashMap) {
const callback = this.async();

fs.writeFile('queryIdMap.json', JSON.stringify(queryMap, null, 2), (err) => {
if (err) return callback(err);

callback(null, allCode);
});
} else {
return allCode;
}
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"chai": "^4.0.2",
"graphql": "^0.13.0",
"mocha": "^3.4.1",
"persistgraphql": "^0.3.11",
"rewire": "^4.0.1",
"rollup": "^0.45.0",
"test-all-versions": "^3.3.2"
},
Expand Down
Loading