Files
30-seconds-of-code/node_modules/babel-plugin-remove-graphql-queries/index.js
2019-08-20 15:52:05 +02:00

324 lines
11 KiB
JavaScript

"use strict";
exports.__esModule = true;
exports.default = _default;
exports.getGraphQLTag = getGraphQLTag;
/* eslint-disable new-cap */
const graphql = require(`gatsby/graphql`);
const murmurhash = require(`./murmur`);
const nodePath = require(`path`);
const isGlobalIdentifier = tag => tag.isIdentifier({
name: `graphql`
}) && tag.scope.hasGlobal(`graphql`);
function getTagImport(tag) {
const name = tag.node.name;
const binding = tag.scope.getBinding(name);
if (!binding) return null;
const path = binding.path;
const parent = path.parentPath;
if (binding.kind === `module` && parent.isImportDeclaration() && parent.node.source.value === `gatsby`) return path;
if (path.isVariableDeclarator() && path.get(`init`).isCallExpression() && path.get(`init.callee`).isIdentifier({
name: `require`
}) && path.get(`init`).node.arguments[0].value === `gatsby`) {
const id = path.get(`id`);
if (id.isObjectPattern()) {
return id.get(`properties`).find(path => path.get(`value`).node.name === name);
}
return id;
}
return null;
}
function isGraphqlTag(tag) {
const isExpression = tag.isMemberExpression();
const identifier = isExpression ? tag.get(`object`) : tag;
const importPath = getTagImport(identifier);
if (!importPath) return isGlobalIdentifier(tag);
if (isExpression && (importPath.isImportNamespaceSpecifier() || importPath.isIdentifier())) {
return tag.get(`property`).node.name === `graphql`;
}
if (importPath.isImportSpecifier()) return importPath.node.imported.name === `graphql`;
if (importPath.isObjectProperty()) return importPath.get(`key`).node.name === `graphql`;
return false;
}
function removeImport(tag) {
const isExpression = tag.isMemberExpression();
const identifier = isExpression ? tag.get(`object`) : tag;
const importPath = getTagImport(identifier);
const removeVariableDeclaration = statement => {
let declaration = statement.findParent(p => p.isVariableDeclaration());
if (declaration) {
declaration.remove();
}
};
if (!importPath) return;
const parent = importPath.parentPath;
if (importPath.isImportSpecifier()) {
if (parent.node.specifiers.length === 1) parent.remove();else importPath.remove();
}
if (importPath.isObjectProperty()) {
if (parent.node.properties.length === 1) {
removeVariableDeclaration(importPath);
} else importPath.remove();
}
if (importPath.isIdentifier()) {
removeVariableDeclaration(importPath);
}
}
function getGraphQLTag(path) {
const tag = path.get(`tag`);
const isGlobal = isGlobalIdentifier(tag);
if (!isGlobal && !isGraphqlTag(tag)) return {};
const quasis = path.node.quasi.quasis;
if (quasis.length !== 1) {
throw new Error(`BabelPluginRemoveGraphQL: String interpolations are not allowed in graphql ` + `fragments. Included fragments should be referenced ` + `as \`...MyModule_foo\`.`);
}
const text = quasis[0].value.raw;
const hash = murmurhash(text, `abc`);
try {
const ast = graphql.parse(text);
if (ast.definitions.length === 0) {
throw new Error(`BabelPluginRemoveGraphQL: Unexpected empty graphql tag.`);
}
return {
ast,
text,
hash,
isGlobal
};
} catch (err) {
throw new Error(`BabelPluginRemoveGraphQLQueries: GraphQL syntax error in query:\n\n${text}\n\nmessage:\n\n${err.message}`);
}
}
function _default({
types: t
}) {
return {
visitor: {
Program(path, state) {
const nestedJSXVistor = {
JSXIdentifier(path2) {
if ([`production`, `test`].includes(process.env.NODE_ENV) && path2.isJSXIdentifier({
name: `StaticQuery`
}) && path2.referencesImport(`gatsby`) && path2.parent.type !== `JSXClosingElement`) {
const identifier = t.identifier(`staticQueryData`);
const filename = state.file.opts.filename;
const shortResultPath = `public/static/d/${this.queryHash}.json`;
const resultPath = nodePath.join(process.cwd(), shortResultPath); // Add query
path2.parent.attributes.push(t.jSXAttribute(t.jSXIdentifier(`data`), t.jSXExpressionContainer(identifier))); // Add import
const importDefaultSpecifier = t.importDefaultSpecifier(identifier);
const importDeclaration = t.importDeclaration([importDefaultSpecifier], t.stringLiteral(filename ? nodePath.relative(nodePath.parse(filename).dir, resultPath) : shortResultPath));
path.unshiftContainer(`body`, importDeclaration);
}
}
};
const nestedHookVisitor = {
CallExpression(path2) {
if ([`production`, `test`].includes(process.env.NODE_ENV) && path2.node.callee.name === `useStaticQuery` && path2.get(`callee`).referencesImport(`gatsby`)) {
const identifier = t.identifier(`staticQueryData`);
const filename = state.file.opts.filename;
const shortResultPath = `public/static/d/${this.queryHash}.json`;
const resultPath = nodePath.join(process.cwd(), shortResultPath); // Remove query variable since it is useless now
if (this.templatePath.parentPath.isVariableDeclarator()) {
this.templatePath.parentPath.remove();
} // Remove imports to useStaticQuery
const importPath = path2.scope.getBinding(`useStaticQuery`).path;
const parent = importPath.parentPath;
if (importPath.isImportSpecifier()) if (parent.node.specifiers.length === 1) parent.remove();else importPath.remove(); // Add query
path2.replaceWith(t.memberExpression(identifier, t.identifier(`data`))); // Add import
const importDefaultSpecifier = t.importDefaultSpecifier(identifier);
const importDeclaration = t.importDeclaration([importDefaultSpecifier], t.stringLiteral(filename ? nodePath.relative(nodePath.parse(filename).dir, resultPath) : shortResultPath));
path.unshiftContainer(`body`, importDeclaration);
}
}
};
const tagsToRemoveImportsFrom = new Set();
const setImportForStaticQuery = templatePath => {
const {
ast,
text,
hash,
isGlobal
} = getGraphQLTag(templatePath);
if (!ast) return null;
const queryHash = hash.toString();
const query = text;
const tag = templatePath.get(`tag`);
if (!isGlobal) {
// Enqueue import removal. If we would remove it here, subsequent named exports
// wouldn't be handled properly
tagsToRemoveImportsFrom.add(tag);
} // Replace the query with the hash of the query.
templatePath.replaceWith(t.StringLiteral(queryHash)); // traverse upwards until we find top-level JSXOpeningElement or Program
// this handles exported queries and variable queries
let parent = templatePath;
while (parent && ![`Program`, `JSXOpeningElement`].includes(parent.node.type)) {
parent = parent.parentPath;
} // modify StaticQuery elements and import data only if query is inside StaticQuery
parent.traverse(nestedJSXVistor, {
queryHash,
query
}); // modify useStaticQuery elements and import data only if query is inside useStaticQuery
parent.traverse(nestedHookVisitor, {
queryHash,
query,
templatePath
});
return null;
}; // Traverse for <StaticQuery/> instances
path.traverse({
JSXElement(jsxElementPath) {
if (jsxElementPath.node.openingElement.name.name !== `StaticQuery`) {
return;
}
jsxElementPath.traverse({
JSXAttribute(jsxPath) {
if (jsxPath.node.name.name !== `query`) {
return;
}
jsxPath.traverse({
TaggedTemplateExpression(templatePath, state) {
setImportForStaticQuery(templatePath);
},
Identifier(identifierPath) {
if (identifierPath.node.name !== `graphql`) {
const varName = identifierPath.node.name;
path.traverse({
VariableDeclarator(varPath) {
if (varPath.node.id.name === varName && varPath.node.init.type === `TaggedTemplateExpression`) {
varPath.traverse({
TaggedTemplateExpression(templatePath) {
setImportForStaticQuery(templatePath);
}
});
}
}
});
}
}
});
}
});
}
}); // Traverse once again for useStaticQuery instances
path.traverse({
CallExpression(hookPath) {
if (hookPath.node.callee.name !== `useStaticQuery` || !hookPath.get(`callee`).referencesImport(`gatsby`)) {
return;
}
hookPath.traverse({
// Assume the query is inline in the component and extract that.
TaggedTemplateExpression(templatePath) {
setImportForStaticQuery(templatePath);
},
// // Also see if it's a variable that's passed in as a prop
// // and if it is, go find it.
Identifier(identifierPath) {
if (identifierPath.node.name !== `graphql`) {
const varName = identifierPath.node.name;
path.traverse({
VariableDeclarator(varPath) {
if (varPath.node.id.name === varName && varPath.node.init.type === `TaggedTemplateExpression`) {
varPath.traverse({
TaggedTemplateExpression(templatePath) {
setImportForStaticQuery(templatePath);
}
});
}
}
});
}
}
});
}
}); // Run it again to remove non-staticquery versions
path.traverse({
TaggedTemplateExpression(path2, state) {
const {
ast,
hash,
isGlobal
} = getGraphQLTag(path2);
if (!ast) return null;
const queryHash = hash.toString();
const tag = path2.get(`tag`);
if (!isGlobal) {
// Enqueue import removal. If we would remove it here, subsequent named exports
// wouldn't be handled properly
tagsToRemoveImportsFrom.add(tag);
} // Replace the query with the hash of the query.
path2.replaceWith(t.StringLiteral(queryHash));
return null;
}
});
tagsToRemoveImportsFrom.forEach(removeImport);
}
}
};
}