Skip to content

Commit

Permalink
Implement Gatsby onCreateNode and createPages APIs
Browse files Browse the repository at this point in the history
To integrate MDX Gatsby's `onCreateNode` () and `createPages` (2) APIs
has been implemented to handle the generation of the GraphQL MDX nodes
and the subsequent automated creation of the docs pages and blog posts.

References:
  (1) https://www.gatsbyjs.org/docs/node-apis/#onCreateNode
  (2) https://www.gatsbyjs.org/docs/node-apis/#createPages

GH-129
  • Loading branch information
arcticicestudio committed Mar 3, 2019
1 parent a2ccb71 commit 1882e6b
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 3 deletions.
108 changes: 108 additions & 0 deletions .gatsby/createPages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
* Copyright (C) 2018-present Sven Greb <[email protected]>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

const { resolve: r } = require("path");

const { nodeFields } = require("../src/config/internal/nodes");
const { ROUTE_BLOG, ROUTE_DOCS, ROUTE_LANDING, ROUTE_ROOT } = require("../src/config/routes/mappings");

const TemplateBlogPost = r(__dirname, "../src/components/templates/blog/BlogPost.jsx");
const TemplateDocsPage = r(__dirname, "../src/components/templates/docs/DocsPage.jsx");

const mdxQuery = `
{
allMdx {
edges {
node {
code {
scope
}
fields {
${Object.keys(nodeFields).map(nf => nf.replace(",", "\n"))}
}
frontmatter {
draft
}
id
}
}
}
}
`;

/**
* Implementation of the Gatsby Node "createPages" API which tells plugins to add pages.
* This extension point is called only after the initial sourcing and transformation of nodes and
* when the creation of the GraphQL schema is complete to allow to query data in order to create pages.
*
* @author Arctic Ice Studio <[email protected]>
* @author Sven Greb <[email protected]>
* @since 0.10.0
* @see https://next.gatsbyjs.org/docs/node-apis/#createPages
* @see https://next.gatsbyjs.org/docs/node-apis/#createPage
* @see https://github.com/ChristopherBiscardi/gatsby-mdx
*/
const createPages = async ({ graphql, actions }) => {
const { createPage, createRedirect } = actions;
const isProductionMode = process.env.NODE_ENV === "production";

/* Always redirect from the landing page to the root route. */
createRedirect({
fromPath: ROUTE_LANDING,
redirectInBrowser: true,
toPath: ROUTE_ROOT
});

const mdxQueryResult = await graphql(mdxQuery);
if (mdxQueryResult.errors) {
throw Error("Error while running GraphQL query for MDX!", mdxQueryResult.errors);
}

mdxQueryResult.data.allMdx.edges.forEach(({ node }) => {
const { id } = node;
const { contentSourceType, date, relativeDirectory, slug, slugParentRoute } = node.fields;
const { draft } = node.frontmatter;

/* Only create non-draft pages in production mode while also create draft pages during development. */
if (draft && isProductionMode) return;

let template;
switch (slugParentRoute) {
case ROUTE_BLOG:
template = TemplateBlogPost;
break;
case ROUTE_DOCS:
template = TemplateDocsPage;
break;
default:
throw Error(`No matching template found while creating page for node with path ${slugParentRoute}${slug}!`);
}

createPage({
path: `${slugParentRoute}${slug}`,
component: template,
/*
* Make the specified fields available as variables in page queries.
*
* @see https://graphql.org/learn/queries/#variables
* @see https://www.gatsbyjs.org/docs/graphql-reference/#query-variables
*/
context: {
contentSourceType,
date,
id,
relativeDirectory,
slug,
slugParentRoute
}
});
});
};

module.exports = createPages;
2 changes: 1 addition & 1 deletion .gatsby/onCreateBabelConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* @since 0.1.0
*/
const onCreateBabelConfig = ({ actions }) => {
const isProductionMode = () => process.env.NODE_ENV === "production";
const isProductionMode = process.env.NODE_ENV === "production";

/*
* Allows to use the "ES Class Fields & Static Properties" proposal to transforms static class properties as well as
Expand Down
116 changes: 116 additions & 0 deletions .gatsby/onCreateNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
* Copyright (C) 2018-present Sven Greb <[email protected]>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

const { createFilePath } = require("gatsby-source-filesystem");

const { nodeFields, sourceInstanceTypes } = require("../src/config/internal/nodes");
const { BASE_DIR_CONTENT, NODE_TYPE_MDX, REGEX_BLOG_POST_DATE } = require("../src/config/internal/constants");
const { ROUTE_BLOG, ROUTE_DOCS } = require("../src/config/routes/mappings");

/**
* Extracts the date of a blog post from the given path using the `REGEX_BLOG_POST_DATE` regular expression.
* Note that the returned date is in UTC format to be independent of the time zone. The exact time of the day will be
* parsed from the blog posts frontmatter "publishTime" field.
*
* @private
* @method extractDateFromPath
* @param {string} path The path from which the blog post date should be extracted.
* @return {string|null} The extracted blog post date in UTC format as JSON string if the given path matches the
* regular expression, `null` otherwise.
*/
const extractBlogPostDateFromPath = path => {
const date = REGEX_BLOG_POST_DATE.exec(path);
return date ? new Date(Date.UTC(date[1], date[2] - 1, date[3])).toJSON() : null;
};

/**
* Implementation of the Gatsby Node "onCreateNode" API which gets called when a new node is created.
* Allows plugins to extend or transform nodes created by other plugins.
*
* @author Arctic Ice Studio <[email protected]>
* @author Sven Greb <[email protected]>
* @since 0.10.0
* @see https://next.gatsbyjs.org/docs/node-apis/#onCreateNode
* @see https://next.gatsbyjs.org/docs/actions/#createNode
* @see https://next.gatsbyjs.org/docs/actions/#createNodeField
* @see https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-filesystem#createfilepath
*/
const onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type === NODE_TYPE_MDX) {
const contentFileResourceSlug = createFilePath({
node,
getNode,
basePath: `${BASE_DIR_CONTENT}`,
trailingSlash: false
});
const { relativeDirectory, relativePath, sourceInstanceName } = getNode(node.parent);

if (sourceInstanceName === sourceInstanceTypes.blog.id) {
const date = extractBlogPostDateFromPath(relativePath);

if (!date) {
throw Error(
`Blog post content resource path '${relativePath}' doesn't match the required date-based directory structure: ${REGEX_BLOG_POST_DATE}`
);
}

createNodeField({
node,
name: `${nodeFields.date.name}`,
value: extractBlogPostDateFromPath(relativePath)
});
createNodeField({
node,
name: `${nodeFields.contentSourceType.name}`,
value: `${sourceInstanceTypes.blog.id}`
});
createNodeField({
node,
name: `${nodeFields.relativeDirectory.name}`,
value: `${relativeDirectory}`
});
createNodeField({
node,
name: `${nodeFields.slug.name}`,
value: `${contentFileResourceSlug}`
});
createNodeField({
node,
name: `${nodeFields.slugParentRoute.name}`,
value: `${ROUTE_BLOG}`
});
}

if (sourceInstanceName === sourceInstanceTypes.docs.id) {
createNodeField({
node,
name: `${nodeFields.contentSourceType.name}`,
value: `${sourceInstanceTypes.docs.id}`
});
createNodeField({
node,
name: `${nodeFields.relativeDirectory.name}`,
value: `${relativeDirectory}`
});
createNodeField({
node,
name: `${nodeFields.slug.name}`,
value: contentFileResourceSlug
});
createNodeField({
node,
name: `${nodeFields.slugParentRoute.name}`,
value: `${ROUTE_DOCS}`
});
}
}
};

module.exports = onCreateNode;
1 change: 0 additions & 1 deletion content/blog/.gitkeep

This file was deleted.

8 changes: 8 additions & 0 deletions content/blog/2019/03/02/query-error-draft-stub/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const frontmatter = {
contentImages: ["./placeholder.png"],
title: "GraphQL query error draft stub",
introduction: "This file must be kept to prevent GraphQL queries to fail when no blog posts are in the filesystem.",
heroImage: "./placeholder.png",
publishTime: "00:00:00+0000",
draft: true
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion content/docs/.gitkeep

This file was deleted.

6 changes: 6 additions & 0 deletions content/docs/query-error-draft-stub/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const frontmatter = {
contentImages: ["./placeholder.png"],
title: "GraphQL query error draft stub",
subline: "This file must be kept to prevent GraphQL queries to fail when no docs pages are in the filesystem.",
draft: true
};
Binary file added content/docs/query-error-draft-stub/placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@

exports.onCreateBabelConfig = require("./.gatsby/onCreateBabelConfig");
exports.onCreateWebpackConfig = require("./.gatsby/onCreateWebpackConfig");
exports.onCreateNode = require("./.gatsby/onCreateNode");
exports.createPages = require("./.gatsby/createPages");

0 comments on commit 1882e6b

Please sign in to comment.