Giter Club home page Giter Club logo

gatsby-plugin-groq's Introduction

gatbsy-plugin-groq

Gatsby plugin for using GROQ in place of GraphQL

The purpose of this plugin is to merge the power of GROQ and Gatsby by allowing developers to run GROQ queries against Gatsby's data layer in their page and static queries. For those of you who are familiar with GROQ, you are probably already in love and need no introduction. For everyone else, I highly suggest reading the below What is This and Resources sections.

Included in this repository is a demo Gatsby starter with some data to play around with. You can find it under packages/gatsby-groq-starter. Just download the files and follow the plugin installation instructions below to start having fun.

View low quality demo here: https://drive.google.com/file/d/1FVch2HbAWk1TEXph1katiNb1uuaBLSHf/view?usp=sharing

πŸŽ‚ Features

  • Works with any data pulled into Gatsby's data layer
  • Replicates Gatsby's beloved patterns
  • GROQ-based page queries with HMR
  • GROQ-based static queries with live reloads
  • Leverages GROQ's native functionality for advanced querying, node/document projections, joins (see notes on joins), etc,
  • String interpolation ("fragments") within queries, much more flexible than GraphQL fragments
  • GROQ explorer in browser during development at locahost:8000/__groq (TO DO)
  • Optimized for incremental builds on Cloud and OSS (TO DO)

πŸš€ Get Started

  1. At the root of your Gatsby project, install from the command line:
npm install --save gatsby-plugin-groq
// or
yarn add gatsby-plugin-groq
  1. In gatbsy-config.js, add the plugin configuration to the plugins array:
module.exports = {
  //...
  plugins: [
    {
      resolve: 'gatsby-plugin-groq',
      options: {
        // Location of your project's fragments index file.
        // Only required if you are implementing fragments. Defaults to `./src/fragments`.
        fragmentsDir: './src/fragments',
        // Determines which field to match when using joins. Defaults to `id`.
        referenceMatcher: 'id',
        // If only using Sanity documents, change this to true to use default method
        // of joining documents without having to append ._ref to the referencing field.
        // Defaults to false.
        autoRefs: false,
      }
    }
  ]
}
  1. To use a GROQ page query, simply add a named groqQuery export to the top of your component file as you would a Gatsby query:
export const groqQuery = `
  ...
`
  1. To use a GROQ static query, use the useGroqQuery hook:
import { useGroqQuery } from 'gatsby-plugin-groq';

export function() {

  const data = useGroqQuery( `
    ...
  ` );

}
  1. For more flexibility and advanced usage check out Fragments

NOTE: If using joins or the Sanity source plugin, please see limitations

πŸ€” What is This?

Gatsby is an amazing tool that has helped advance modern web development in significant ways. While many love it for its magical frontend concoction of static generation and rehydration via React, easy routing, smart prefetching, image rendering, etc., one of the key areas where it stands out from other similar tools is its GraphQL data layer. This feature is a large part of why some developers love Gatsby and why others choose to go in another direction. Being able to source data from multiple APIs, files, etc. and compile them altogether into a queryable GraphQL layer is amazing, but many developers simply don't enjoy working with GraphQL. This is where GROQ comes in.

GROQ (Graph-Relational Object Queries) is an incredibly robust and clear general query language designed by the folks at Sanity for filtering and projecting JSON data. In many ways it is very similar to GraphQL in that you can run multiple robust queries and specify the data you need all within a single request, however with GROQ you can accomplish much more in a smoother and more flexible way. It supports complex parameters and operators, functions, piping, advanced joins, slicing, ordering, projections, conditionals, pagination etc., all with an intuitive syntax 😲.

For example, take this somewhat simple GraphQL query:

{
  authors(where: {
      debutedBefore_lt: "1900-01-01T00:00:00Z",
      name_matches: "Edga*r"
  ) {
    name,
    debutYear,
  }
}

Here is what it would look like using GROQ:

*[_type == "author" && name match "Edgar" && debutYear < 1900]{
  name,
  debutYear
}

The more complex the queries, the more GROQ's advantages shine. This is why some developers already familiar with GROQ bypass Gatsby's data layer so that they could leverage its power.

πŸ§™ How it Works

This plugin mimics Gatsby's own method of extracting queries from components by using a few Babel tools to parse files and traverse code to capture all queries found in files. By leveraging Gatsby's Node APIs and helpers we are able to extract queries from files on demand then run them against all GraphQL nodes found in Gatsby's redux store. After queries are run we can either feed results into a page's context (page queries), or cache for later use within individual components (static queries). Everything was done to leverage available APIs and to avoid interacting with Gatsby's data store directly as much as possible.

For now, all cache related to groq queries can be found in .cache/groq during development and public/static/groq in production.

Page Queries

All page-level components with groqQuery exports will have their queries (template literal) extracted and cached unprocessed as a hashed json file in the groq directory. The hash is based on the component file's full path so that the cached file can always be associated with the component. During bootstrap, whenever a page is created via createPage the plugin checks the cache to see if there is a page query related to it page's component. If there is, it then runs the query and replaces any variables with values supplied in the page's context. The result is stored in the data property within pageContext.

For example, if a page query contains *[ _type == "page" && _id == $_id ]{ ... } and a page is created with context: { _id: "123" }, the plugin will include the variable and run the query: *[ _type == "page" && _id == "123" ]{ ... }.

During development all files are watched for changes. Whenever there is a change to a file containing a page query the above process runs again and the updated data is injected into all paths that contain the changed component.

Static Queries

All components using the hook useGroqQuery first have these queries extracted, processed, and cached during bootstrap. However unlike page queries, static query hashes are based off of the queries themselves and contain the actual results of their queries after they have been run. If a static query changes, it generates a new hashed result. During SSR and runtime, the useGroqQuery function then retrieves the cache associated to its query and returns the result.

Similar to page queries, all files are watched for changes and whenever there is a change to a file containing a static query the above process runs again, the query results are cached, and the page refreshes with the static query now returning the updated content.

Fragments

Playing off of GraphQL, "fragments" are strings of reusable portions of GROQ queries that can be interpolated within other queries. For example, say you have a blog where you are showing post snippets throughout multiple page templates and for every post need to retrieve its id, title, summary, and category, along with the category's name and slug. Instead of having to remember which fields you need and write this out every time, you could create a reusable fragment:

exports.postSnippetFields = `
  id,
  summary,
  title,
  "category": *[ type == "category" && id == ^.category ] {
    name,
    slug
  }
`

Then simply reuse the fragment wherever you need:

import { postSnippetFields } from 'src/fragments';

const groqQuery = `
  *[ type == "post" ] {
    ${postSnippetFields}
  }

To use GROQ fragments with this plugin, for now all fragments must be exported from a index.js using CommonJS syntax. You must also specify the directory where this file is found within the plugin options: fragmentsDir: // Directory relative to project root.

That should cover most of it. Check the comments within code for more details.

🀦 Limitations

Joins

The ability to join multiple documents and retrieve their fields is a popular feature of GROQ. More testing needs to be done, but currently most join syntaxes are supported other than the references() function.

For Sanity users, if using the -> operator for everything other than file assets (i.e. images) you will need to append ._ref to the field which contains the reference. So with a Sanity dataset, the usual referenceField->{ ... } would instead look like this: referenceField._ref->{ ... }. Likewise for arrays: arrayOfReferences[]._ref->{ ... }. If you are only using Sanity data within your Gatsby project and would like to use the regular syntax, within the plugin options set autoRefs: true and you won't have to worry about appending the extra field.

Other usage with gatsby-source-sanity

For every _ref field within your documents the source plugin injects the referenced document's GraphQL node id instead of its default _id value. This means that whenever you are trying to match _ref values to documents you need to use the id field instead of _id.

So instead of what you are used to:

{
  ...,
  "document: *[ _id == ^._ref ] {
    ...
  }[0]
}

You would use this:

{
  ...,
  "document: *[ id == ^._ref ] {
    ...
  }[0]
}

If you are using the source plugin and running issues into issues with your joins, if all else fails try double checking your queries and make sure you are matching every _ref to the actual node id.

βŒ› TO DO (random order)

  • Get rid of relative directories
  • Work on issues with joins we might be limited here
  • Clean up spotty caching issues after running development
  • Experiment with other data sources (WordPress)
  • GROQ explorer
  • Allow for fragment functions
  • Set up page refreshing when fragments are changed
  • Look into using esm for ES6 imports/exports
  • Set up an option to auto-resolve references?
  • Error messaging (especially when there are Babel parsing errors)
  • Performance optimizations
  • Improve docs
  • Provide recipe docs with heavy use of fragments
  • Incremental builds?
  • Allow for variables within static queries?
  • Helpers for real-time listening in client (Sanity only)
  • Tests
  • Proselytize everyone from GraphQL to GROQ.

πŸ“– GROQ Resources

πŸ™‡ Huge Thanks

Thanks to the awesome teams at Gatsby and Sanity for their absolutely amazing tools and developer support. If you haven't checked it out yet, I would HIGHLY recommend looking into Sanity's incredible CMS. It's hard to imagine how a headless CMS experience could be any better.

gatsby-plugin-groq's People

Contributors

kenjonespizza avatar kmcaloon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

gatsby-plugin-groq's Issues

Cannot find module 'gatsby-cli/lib/reporter'

Got the following stack trace when running yarn start on a sanity/gatsby project

gatsby_1  |   Error: Cannot find module 'gatsby-cli/lib/reporter'
gatsby_1  |   Require stack:
gatsby_1  |   - /code/web/plugins/gatsby-plugin-groq/gatsby-node.js
gatsby_1  |   - /code/web/node_modules/gatsby/dist/bootstrap/resolve-module-exports.js
gatsby_1  |   - /code/web/node_modules/gatsby/dist/bootstrap/load-plugins/validate.js
gatsby_1  |   - /code/web/node_modules/gatsby/dist/bootstrap/load-plugins/load.js
gatsby_1  |   - /code/web/node_modules/gatsby/dist/bootstrap/load-plugins/index.js
gatsby_1  |   - /code/web/node_modules/gatsby/dist/bootstrap/index.js
gatsby_1  |   - /code/web/node_modules/gatsby/dist/commands/develop.js
gatsby_1  |   - /code/web/node_modules/gatsby/node_modules/gatsby-cli/lib/create-cli.js
gatsby_1  |   - /code/web/node_modules/gatsby/node_modules/gatsby-cli/lib/index.js
gatsby_1  |   - /code/web/node_modules/gatsby/dist/bin/gatsby.js

busted in Typescript?

When I try yarn develop with my index file named index.tsx, I get this error:

warn Error parsing file: /Users/wvgg/projects/franklyn/frontent.groq/src/pages/index.tsx

Works fine if I rename to index.jsx

I had to abandon using this plugin as I'm in a bit of a hurry, but my gawd, I was able to do in 20 lines of groq what took 70 in graphql… what a disaster of a spec…

Netlify build error

Got this error on a build randomly. Works noramlly.

2:11:16 PM: error "gatsby-plugin-groq" threw an error while running the resolvableExtensions lifecycle:
2:11:16 PM: ENOTEMPTY: directory not empty, rmdir '/opt/build/repo/web/public/static/groq'
2:11:16 PM:   23 | 
2:11:16 PM:   24 |   if( !! fs.existsSync( GROQ_DIR ) ) {
2:11:16 PM: > 25 |     fs.rmdirSync( GROQ_DIR, { recursive: true } );
2:11:16 PM:      |        ^
2:11:16 PM:   26 |   }
2:11:16 PM:   27 |   fs.mkdirSync( GROQ_DIR );
2:11:16 PM:   28 | 
2:11:16 PM: 
2:11:16 PM: 
2:11:16 PM:   Error: ENOTEMPTY: directory not empty, rmdir '/opt/build/repo/web/public/stati  c/groq

Queries performed and cached properly, but not being exposed to components

Hey there, Kevin! πŸ‘‹

First of all, let me reinforce the gratitude for this. I'm about to go insane managing a bunch of needlessly complex GraphQL queries and React methods to work with sophisticated data structures that GROQ has no problems with, and having this plugin at hand can be a life-saver.

That said, I've managed to get querying right, but the results are never exposed to the pages and components. Here are two examples:

// First, components w/ static query
import React from 'react';
import { useGroqQuery } from 'gatsby-plugin-groq';

export default () => {
  const test = useGroqQuery(`
    *[0..2]
  `);
  console.log({ test }); // undefined
  return <>{JSON.stringify(test)}</>;
};
// Second, standalone pages (src/pages/test.js)
import React from 'react';

export default (props) => {
  console.log(props); // no props.data nor props.pageContext.data
  return <>{JSON.stringify(props)}</>;
};

export const groqQuery = `
  *[0..2]
`;

I've also tested this with programmatically created pages (createPage in gatsby-node), the result is the same: queries are cached and properly fetched, but they don't show up in the front-end.

How can I help debugging this?

Here are my enabled plugins in gatsby-config:

[
  {
    resolve: "gatsby-source-filesystem",
    options: {
      name: "content",
      path: `${__dirname}/docs-structure`,
    },
  },
  {
    resolve: "gatsby-source-filesystem",
    options: {
      name: "tutorials",
      path: `${__dirname}/tutorials`,
    },
  },
  {
    resolve: "gatsby-source-filesystem",
    options: {
      name: "resources",
      path: `${__dirname}/resources`,
    },
  },
  {
    resolve: `gatsby-plugin-mdx`,
    options: {
      extensions: [`.md`, `.mdx`],
      gatsbyRemarkPlugins: [
        "gatsby-remark-autolink-headers",
        {
          resolve: "gatsby-remark-external-links",
          options: {
            target: "_blank",
          },
        },
      ],
    },
  },
  {
    resolve: "gatsby-transformer-remark",
    options: {
      plugins: [
        "gatsby-remark-autolink-headers",
        {
          resolve: "gatsby-remark-external-links",
          options: {
            target: "_blank",
          },
        },
      ],
    },
  },
  {
    resolve: `gatsby-plugin-sitemap`,
    options: {},
  },
  "gatsby-plugin-preact",
  "gatsby-plugin-react-helmet",
  {
    resolve: "gatsby-source-sanity",
    options: {
      projectId: "e3vd3ukt",
      dataset: "production",
    },
  },
  {
    resolve: "gatsby-plugin-groq",
    options: {
      // referenceMatcher: '_id',
      autoRefs: true,
    },
  },
  {
    resolve: "gatsby-plugin-manifest",
    options: {
      legacy: false,
    },
  },
  {
    resolve: "gatsby-plugin-google-tagmanager",
    options: {
      id: "GTM-TPMQ838",

      // Include GTM in development.
      // Defaults to false meaning GTM will only be loaded in production.
      includeInDevelopment: false,
    },
  },
  "gatsby-plugin-less",
  {
    resolve: `gatsby-plugin-netlify`,
    options: {
      // Reset the default rule of preventing framing, as we need it for CMS content preview
      headers: {
        "/preview": ["X-Frame-Options: ALLOW"],
      },
    },
  },
];

Not getting any data back in production?

Hi! Hey this doesn't seem to be a very google-able plugin so thought I'd ask here about how to debug. I am using Sanity as my CMS.

I'm getting data back when I'm on localhost but when I deploy on netlify I'm not getting any data back on the production site?

I'm pretty new to web dev in general so don't know the right things to look for to debug this and was just looking for general advice?

gatsby-config.js

{
  resolve: `gatsby-plugin-groq`,
  options: {
    referenceMatcher: 'id',
    autoRefs: false,
  },
},

I'm using static queries on pages that are built via createPages in gatsby-node.js. So for example I have just a simple "site directory" list with like the standard "about", "contact" etc. pages. I'm then querying Sanity for the content to put on the pages.

So gatsby-node.js:

async function turnPagesIntoPages({ graphql, actions }) {
  // 1. get template for page.
  const pageTemplate = path.resolve('./src/templates/Page.js');

  // 2. query all pages
  const { data } = await graphql(`
    query {
      pages: allSanityPage {
        nodes {
          name
          slug {
            current
          }
        }
      }
    }
  `);

  // 3. Loop over each page and create a page for it
  data.pages.nodes.forEach((page) => {
    actions.createPage({
      path: `${page.slug.current}`,
      component: pageTemplate,
      context: {
        name: page.name,
      },
    });
  });
}

Then the Page.js Component:

...
import { useGroqQuery } from 'gatsby-plugin-groq';

export default function Page({ data }) {
  const pages = useGroqQuery(`
    *[_type == "page"]
  `);
  console.log({ pages });

  const page = pages.filter((i) => i.slug.current === data.page.slug.current);

  return (
    <div>
      {page[0]?.body ? (
        <PortableText blocks={page[0].body} serializers={serializers} />
      ) : (
        <p>Page coming soon πŸ˜ƒ</p>
      )}
    </div>
  );
}

So when I see that console.log in my netlify its empty, but this works on localhost.

As I said I'm really new to this so lmk what else I can provide to help debug?

Not maintained

Hi, this doesn't work in latest gatsby can you put a disclaimer at the top of the readme that its not maintained? Thanks!

Error occurred when building on Gatsby Cloud

Hi there,

It me again. I happened to be trying out gatsby cloud to check out the sanity live preview and noticed that the build had an error related to this plugin.

"gatsby-plugin-groq" threw an error while running the resolvableExtensions lifecycle: ENOENT: no such file or directory, mkdir 'undefined/public/static/groq' 26 | fs.rmdirSync( GROQ_DIR, { recursive: true } ); 27 | } > 28 | fs.mkdirSync( GROQ_DIR ); | ^ 29 | 30 | // Cache fragments. 31 | const fragmentsDir = !! plugin.fragmentsDir ? path.join( ROOT, plugin.fragmentsDir ) : null;

Just thought I would let you know.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.