Published
What I learned building a Gatsby plugin to source my collection of board games through BoardGameGeek's API.
On this site there's a page where you can see my
collection of board games. The data comes from
my BoardGameGeek profile
using
gatsby-source-bgg
.
gatsby-source-bgg
is a Gatsby source plugin I built and open
sourced following Gatsby's official
documentation on creating a source plugin.
Here are two things I wish I didn't have to find out after hours of debugging: how to write the plugin entirely in Typescript, and how to fix an Invariant Violation error.
I got off the ground very quickly, and I thought that's it, I'm done, v1.0.0 is ready to go out.
I built a few helper functions to download data with from the
external API, and I set up the plugin to pull images from the
remote location so they could be optimized with the
gatsby-plugin-image
, rather than hotlinked.
And here started my troubles: every time I tried to build the site without clearing the cache, the process would fail with an Invariant Violation error.
To make sense of this error, I first rewrote every remaining bit
of JavaScript in Typescript, and later figured out I was missing
an important parameter when calling createRemoteFileNode
from
gatsby-source-filesystem
.
How to write the entire plugin in Typescript
Gatsby in my experience is not the most Typescript friendly framework, and the guide to build a source plugin follows suit and doesn't mention Typescript. It is possible though to build Gatsby plugins in Typescript. I just wish I knew it sooner.
In Gatsby source plugins, it's important that your package
contains a gatsby-node.js
file in the root directory.
I initially wrote my gatsby-node.js
in JavaScript, importing my
functions from the build directory after transpiling all
Typescript files.
I wasn't happy with this structure as functions in
gatsby-node.js
are not well documented and I don't like
mindlessly copy-pasting stuff. In the end I managed to port
everything to gatsby-node.ts
.
Here's how I set up the file structure:
gatsby-source-bgg
├── dist/
│ ├── gatsby-node.js
│ └── index.js
├── gatsby-node.js
├── src/
│ ├── gatsby-node.ts
│ └── index.ts
├── package.json
├── tsconfig.json
└── yarn.lock
As you can see, I still have gatsby-node.js
at the root of the
package, but it simply imports and exports again the required
functions.
const {
sourceNodes,
onCreateNode,
createSchemaCustomization,
} = require("./dist/gatsby-node");
exports.sourceNodes = sourceNodes;
exports.onCreateNode = onCreateNode;
exports.createSchemaCustomization = createSchemaCustomization;
You can find the full source in the repo.
Invariant Violation
I wish I could find the solution to the next error on Google, maybe now it'll show up.
I was happy with my plugin. I published it to the npm registry. I added it to my site. I built once, smooth sailing. I built twice and encoutered this error:
Missing onError handler for invocation 'building-schema', error was 'Invariant Violation: Encountered an error trying to infer a GraphQL type for:
coverImage___NODE
. There is no corresponding node with theid
field matching: "[...list of > IDs]"'
It would occur every time I tried to build my site without
clearing Gatsby's .cache
folder first.
The solution is pretty simple and is actually correctly implemented in Gatsby's docs (which I now realize I should've read more carefully).
When creating a node that contains images, you'll want to pull a
local copy of all remote images instead of simply storing the
URL. This allows you to perform transformations and optimizations
at build time using gatsby-plugin-image
. To do so you'll want
to use createRemoteFileNode
from gatsby-source-filesystem
.
As of right now in gatsby-source-filesystem
v3.7.1, these are
the arguments you can pass to createRemoteFileNode
:
// From gatsby-source-filesystem's source code https://github.com/gatsbyjs/gatsby/blob/54d4721462b9303fed723fdcb15ac5d72e103778/packages/gatsby-source-filesystem/index.d.ts
export interface CreateRemoteFileNodeArgs {
url: string;
store: Store;
// TODO: use GatsbyCache type (requires [email protected])
cache: NodePluginArgs["cache"];
createNode: Function;
createNodeId: Function;
parentNodeId?: string;
auth?: {
htaccess_user: string;
htaccess_pass: string;
};
httpHeaders?: object;
ext?: string;
name?: string;
reporter: object;
}
I had mistakenly left out parentNodeId
, which led to the error
above as between builds the cached images would lose their
relationship to their parent node.
I was once again bailed out by Typescript, and now the package seems to be free of bugs. If you check it out, let me know!