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

feat(gatsby): Allow alternative import syntax for useStaticQuery #20330

Merged
merged 5 commits into from
Jan 7, 2020
Merged
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
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future reference (not blocking on this right now) we can do const node = binding.path?.node here now :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok! Pretty nice indeed!

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