Skip to content

Commit

Permalink
feat(gatsby): Allow alternative import syntax for useStaticQuery (#20330
Browse files Browse the repository at this point in the history
)

* Add failing test

* Allow alternative import syntax

* Refactor

* Also fix the gatsby package

* Incorporate feedback and allow export { binding } syntax for page queries
  • Loading branch information
jfrolich authored and pvdz committed Jan 7, 2020
1 parent 47d2a8d commit 17eaa72
Show file tree
Hide file tree
Showing 6 changed files with 420 additions and 88 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Allow alternative import of useStaticQuery 1`] = `
"import staticQueryData from \\"public/static/d/2626356014.json\\";
import React from 'react';
import * as Gatsby from 'gatsby';
export default (() => {
const siteTitle = staticQueryData.data;
return React.createElement(\\"h1\\", null, siteTitle.site.siteMetadata.title);
});"
`;

exports[`Doesn't add data import for non static queries 1`] = `
"import staticQueryData from \\"public/static/d/4279313589.json\\";
import React from 'react';
Expand Down
35 changes: 25 additions & 10 deletions packages/babel-plugin-remove-graphql-queries/src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ it(`Transforms queries in useStaticQuery`, () => {
export default () => {
const siteTitle = useStaticQuery(graphql\`{site { siteMetadata { title }}}\`)
return (
<h1>{siteTitle.site.siteMetadata.title}</h1>
)
Expand Down Expand Up @@ -79,7 +79,7 @@ it(`Transforms queries defined in own variable in useStaticQuery`, () => {
export default () => {
const query = graphql\`{site { siteMetadata { title }}}\`
const siteTitle = useStaticQuery(query)
return (
<h1>{siteTitle.site.siteMetadata.title}</h1>
)
Expand All @@ -95,7 +95,7 @@ it(`Transforms queries and preserves destructuring in useStaticQuery`, () => {
export default () => {
const query = graphql\`{site { siteMetadata { title }}}\`
const { site } = useStaticQuery(query)
return (
<h1>{site.siteMetadata.title}</h1>
)
Expand All @@ -111,7 +111,7 @@ it(`Transforms queries and preserves variable type in useStaticQuery`, () => {
export default () => {
const query = graphql\`{site { siteMetadata { title }}}\`
let { site } = useStaticQuery(query)
return (
<h1>{site.siteMetadata.title}</h1>
)
Expand Down Expand Up @@ -142,18 +142,18 @@ it(`Transforms only the call expression in useStaticQuery`, () => {
matchesSnapshot(`
import React from "react"
import { graphql, useStaticQuery } from "gatsby"
const useSiteMetadata = () => {
return useStaticQuery(
graphql\`{site { siteMetadata { title }}}\`
).site.siteMetadata
}
export default () => {
const siteMetadata = useSiteMetadata()
return <h1>{siteMetadata.title}</h1>
}
}
`)
})

Expand All @@ -165,7 +165,23 @@ it(`Only runs transforms if useStaticQuery is imported from gatsby`, () => {
export default () => {
const query = graphql\`{site { siteMetadata { title }}}\`
const siteTitle = useStaticQuery(query)
return (
<h1>{siteTitle.site.siteMetadata.title}</h1>
)
}
`)
})

it(`Allow alternative import of useStaticQuery`, () => {
matchesSnapshot(`
import React from 'react'
import * as Gatsby from 'gatsby'
export default () => {
const query = Gatsby.graphql\`{site { siteMetadata { title }}}\`
const siteTitle = Gatsby.useStaticQuery(query)
return (
<h1>{siteTitle.site.siteMetadata.title}</h1>
)
Expand Down Expand Up @@ -271,7 +287,6 @@ it(`distinguishes between the right tags`, () => {
}
\`;
export const query = graphql\`
{
site { siteMetadata { title }}
Expand Down
99 changes: 64 additions & 35 deletions packages/babel-plugin-remove-graphql-queries/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,19 @@ function getGraphQLTag(path) {
}
}

function isUseStaticQuery(path) {
return (
(path.node.callee.type === `MemberExpression` &&
path.node.callee.property.name === `useStaticQuery` &&
path
.get(`callee`)
.get(`object`)
.referencesImport(`gatsby`)) ||
(path.node.callee.name === `useStaticQuery` &&
path.get(`callee`).referencesImport(`gatsby`))
)
}

export default function({ types: t }) {
return {
visitor: {
Expand Down Expand Up @@ -202,8 +215,7 @@ export default function({ types: t }) {
CallExpression(path2) {
if (
[`production`, `test`].includes(process.env.NODE_ENV) &&
path2.node.callee.name === `useStaticQuery` &&
path2.get(`callee`).referencesImport(`gatsby`)
isUseStaticQuery(path2)
) {
const identifier = t.identifier(`staticQueryData`)
const filename = state.file.opts.filename
Expand All @@ -215,12 +227,21 @@ export default function({ types: t }) {
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()
// only remove the import if its like:
// import { useStaticQuery } from 'gatsby'
// but not if its like:
// import * as Gatsby from 'gatsby'
// because we know we can remove the useStaticQuery import,
// but we don't know if other 'gatsby' exports are used, so we
// cannot remove all 'gatsby' imports.
if (path2.node.callee.type !== `MemberExpression`) {
// 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(
Expand Down Expand Up @@ -337,42 +358,50 @@ export default function({ types: t }) {
},
})

function followVariableDeclarations(binding) {
const node = binding.path ? binding.path.node : undefined
if (
node &&
node.type === `VariableDeclarator` &&
node.id.type === `Identifier` &&
node.init.type === `Identifier`
) {
return followVariableDeclarations(
binding.path.scope.getBinding(node.init.name)
)
}
return binding
}

// Traverse once again for useStaticQuery instances
path.traverse({
CallExpression(hookPath) {
if (!isUseStaticQuery(hookPath)) return

function TaggedTemplateExpression(templatePath) {
setImportForStaticQuery(templatePath)
}

// See if the query is a variable that's being passed in
// and if it is, go find it.
if (
hookPath.node.callee.name !== `useStaticQuery` ||
!hookPath.get(`callee`).referencesImport(`gatsby`)
hookPath.node.arguments.length === 1 &&
hookPath.node.arguments[0].type === `Identifier`
) {
return
const [{ name: varName }] = hookPath.node.arguments

let binding = hookPath.scope.getBinding(varName)

if (binding) {
followVariableDeclarations(binding).path.traverse({
TaggedTemplateExpression,
})
}
}

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)
},
})
}
},
})
}
},
TaggedTemplateExpression,
})
},
})
Expand Down
Loading

0 comments on commit 17eaa72

Please sign in to comment.