Skip to content

Commit

Permalink
Import graphql -> master (#6071)
Browse files Browse the repository at this point in the history
* require graphql import

* rollback breaking change

* better runtime error

* MOAR EDGE CASES!
  • Loading branch information
jquense authored and KyleAMathews committed Jun 21, 2018
1 parent 395269d commit 2336095
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Leaves other graphql tags alone 1`] = `
"import React from 'react';
import { graphql } from 'relay';
export default (() => React.createElement(\\"div\\", null, data.site.siteMetadata.title));
export const query = graphql\`
{
site { siteMetadata { title }}
}
\`;"
`;

exports[`Transforms queries in <StaticQuery> 1`] = `
"import staticQueryData from \\"public/static/d/2626356014.json\\";
import React from 'react';
Expand All @@ -11,8 +22,14 @@ export default (() => React.createElement(StaticQuery, {
}));"
`;
exports[`Transforms queries in page components 1`] = `
"import React from 'react';
export default (() => React.createElement(\\"div\\", null, data.site.siteMetadata.title));
export const query = \\"3227941719\\";"
`;
exports[`Transforms queries in page components 1`] = `"export const query = \\"3687030656\\";"`;
exports[`allows the global tag 1`] = `"export const query = \\"3687030656\\";"`;
exports[`handles import aliasing 1`] = `"export const query = \\"3687030656\\";"`;
exports[`handles require 1`] = `"export const query = \\"3687030656\\";"`;
exports[`handles require alias 1`] = `"export const query = \\"3687030656\\";"`;
exports[`handles require namespace 1`] = `"export const query = \\"3687030656\\";"`;
144 changes: 111 additions & 33 deletions packages/babel-plugin-remove-graphql-queries/src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,122 @@ const babel = require(`babel-core`)
const reactPreset = require(`@babel/preset-react`)
const plugin = require(`../`)

var staticQuery = `
import React from 'react'
import { StaticQuery } from 'gatsby'
export default () => (
<StaticQuery
query={graphql\`{site { siteMetadata { title }}}\`}
render={data => <div>{data.site.siteMetadata.title}</div>}
/>
)
`

var pageComponent = `
import React from 'react'
export default () => (
<div>{data.site.siteMetadata.title}</div>
)
export const query = graphql\`
{
site { siteMetadata { title }}
}
\`
`

it(`Transforms queries in <StaticQuery>`, () => {
const { code } = babel.transform(staticQuery, {
function matchesSnapshot(query) {
const { code } = babel.transform(query, {
presets: [reactPreset],
plugins: [plugin],
})
expect(code).toMatchSnapshot()
}

it(`Transforms queries in <StaticQuery>`, () => {
matchesSnapshot(`
import React from 'react'
import { graphql, StaticQuery } from 'gatsby'
export default () => (
<StaticQuery
query={graphql\`{site { siteMetadata { title }}}\`}
render={data => <div>{data.site.siteMetadata.title}</div>}
/>
)
`)
})

it(`Transforms queries in page components`, () => {
const { code } = babel.transform(pageComponent, {
presets: [reactPreset],
plugins: [plugin],
})
expect(code).toMatchSnapshot()
matchesSnapshot(`
import { graphql } from 'gatsby'
export const query = graphql\`
{
site { siteMetadata { title }}
}
\`
`)
})

it(`allows the global tag`, () => {
matchesSnapshot(
`
export const query = graphql\`
{
site { siteMetadata { title }}
}
\`
`
)
})

it(`handles import aliasing`, () => {
matchesSnapshot(
`
import { graphql as gql } from 'gatsby'
export const query = gql\`
{
site { siteMetadata { title }}
}
\`
`
)
})

it(`handles require`, () => {
matchesSnapshot(
`
const { graphql } = require('gatsby')
export const query = graphql\`
{
site { siteMetadata { title }}
}
\`
`
)
})

it(`handles require namespace`, () => {
matchesSnapshot(
`
const Gatsby = require('gatsby')
export const query = Gatsby.graphql\`
{
site { siteMetadata { title }}
}
\`
`
)
})
it(`handles require alias`, () => {
matchesSnapshot(
`
const { graphql: gql } = require('gatsby')
export const query = gql\`
{
site { siteMetadata { title }}
}
\`
`
)
})

it(`Leaves other graphql tags alone`, () => {
matchesSnapshot(
`
import React from 'react'
import { graphql } from 'relay'
export default () => (
<div>{data.site.siteMetadata.title}</div>
)
export const query = graphql\`
{
site { siteMetadata { title }}
}
\`
`
)
})
96 changes: 92 additions & 4 deletions packages/babel-plugin-remove-graphql-queries/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,93 @@ const graphql = require(`gatsby/graphql`)
const murmurhash = require(`./murmur`)
const nodePath = require(`path`)

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 (
tag.scope.hasGlobal(`graphql`) && tag.isIdentifier({ name: `graphql` })
)

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)

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)
importPath.findParent(p => p.isVariableDeclaration())?.remove()
else importPath.remove()
}
if (importPath.isIdentifier()) {
importPath.findParent(p => p.isVariableDeclaration())?.remove()
}
}

function getGraphQLTag(path) {
const tag = path.get(`tag`)
if (!tag.isIdentifier({ name: `graphql` })) return {}
const isGlobal = tag.scope.hasGlobal(`graphql`)

if (!isGlobal && !isGraphqlTag(tag)) return {}

const quasis = path.node.quasi.quasis

Expand All @@ -26,7 +110,7 @@ function getGraphQLTag(path) {
if (ast.definitions.length === 0) {
throw new Error(`BabelPluginRemoveGraphQL: Unexpected empty graphql tag.`)
}
return { ast, text, hash }
return { ast, text, hash, isGlobal }
} catch (err) {
throw new Error(
`BabelPluginRemoveGraphQLQueries: GraphQL syntax error in query:\n\n${text}\n\nmessage:\n\n${
Expand All @@ -44,7 +128,8 @@ export default function({ types: t }) {
JSXIdentifier(path2) {
if (
[`production`, `test`].includes(process.env.NODE_ENV) &&
path2.isJSXIdentifier({ name: `StaticQuery` })
path2.isJSXIdentifier({ name: `StaticQuery` }) &&
path2.referencesImport(`gatsby`)
) {
const identifier = t.identifier(`staticQueryData`)
const filename = state.file.opts.filename
Expand Down Expand Up @@ -79,13 +164,16 @@ export default function({ types: t }) {

path.traverse({
TaggedTemplateExpression(path2, state) {
const { ast, text, hash } = getGraphQLTag(path2)
const { ast, text, hash, isGlobal } = getGraphQLTag(path2)

if (!ast) return null

const queryHash = hash.toString()
const query = text

const tag = path2.get(`tag`)
if (!isGlobal) removeImport(tag)

// Replace the query with the hash of the query.
path2.replaceWith(t.StringLiteral(queryHash))

Expand Down
10 changes: 10 additions & 0 deletions packages/gatsby/src/cache-dir/gatsby-browser-entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,19 @@ StaticQuery.propTypes = {
children: PropTypes.func,
}

function graphql() {
throw new Error(
`It appears like Gatsby is misconfigured. Gatsby related \`graphql\` calls ` +
`are supposed to only be evaluated at compile time, and then compiled away,. ` +
`Unfortunately, something went wrong and the query was left in the compiled code.\n\n.` +
`Unless your site has a complex or custom babel/Gatsby configuration this is likely a bug in Gatsby.`
)
}

export {
Link,
withPrefix,
graphql,
push,
replace,
navigateTo, // TODO: remove navigateTo for v3
Expand Down
Loading

0 comments on commit 2336095

Please sign in to comment.