Note: These are the gatsby plugins that power the file system of this website! See more in the Docs section.
Code:
Gatsby Source File System
js
const path = require('path');
const fs = require('fs');
const { createFilePath } = require('gatsby-source-filesystem');
const _ = require('lodash');
function findFileNode({ node, getNode }) {
let fileNode = node;
let ids = [fileNode.id];
while (fileNode && fileNode.internal.type !== `File` && fileNode.parent) {
fileNode = getNode(fileNode.parent);
if (!fileNode) {
break;
}
if (_.includes(ids, fileNode.id)) {
console.log(`found cyclic reference between nodes`);
break;
}
ids.push(fileNode.id);
}
if (!fileNode || fileNode.internal.type !== `File`) {
console.log('did not find ancestor File node');
return null;
}
return fileNode;
}
exports.onCreateNode = ({ node, getNode, actions }, options) => {
const { createNodeField } = actions;
if (node.internal.type === 'MarkdownRemark') {
let fileNode = findFileNode({ node, getNode });
if (!fileNode) {
throw new Error('could not find parent File node for MarkdownRemark node: ' + node);
}
let url;
if (node.frontmatter.url) {
url = node.frontmatter.url;
} else if (_.get(options, 'uglyUrls', false)) {
url = path.join(fileNode.relativeDirectory, fileNode.name + '.html');
} else {
url = createFilePath({ node, getNode });
}
createNodeField({ node, name: 'url', value: url });
createNodeField({
node,
name: 'absolutePath',
value: fileNode.absolutePath
});
createNodeField({
node,
name: 'relativePath',
value: fileNode.relativePath
});
createNodeField({ node, name: 'absoluteDir', value: fileNode.dir });
createNodeField({
node,
name: 'relativeDir',
value: fileNode.relativeDirectory
});
createNodeField({ node, name: 'base', value: fileNode.base });
createNodeField({ node, name: 'ext', value: fileNode.ext });
createNodeField({ node, name: 'name', value: fileNode.name });
}
};
exports.createPages = ({ graphql, getNode, actions, getNodesByType }) => {
const { createPage, deletePage } = actions;
// Use GraphQL to bring only the "id" and "html" (added by gatsby-transformer-remark)
// properties of the MarkdownRemark nodes. Don't bring additional fields
// such as "relativePath". Otherwise, Gatsby's GraphQL resolvers might infer
// types these fields as File and change their structure. For example, the
// "html" attribute exists only on a GraphQL node, but does not exist on the
// underlying node.
return graphql(`
{
allMarkdownRemark {
edges {
node {
id
html
}
}
}
}
`).then((result) => {
if (result.errors) {
return Promise.reject(result.errors);
}
const nodes = result.data.allMarkdownRemark.edges.map(({ node }) => node);
const siteNode = getNode('Site');
const siteDataNode = getNode('SiteData');
const sitePageNodes = getNodesByType('SitePage');
const sitePageNodesByPath = _.keyBy(sitePageNodes, 'path');
const siteData = _.get(siteDataNode, 'data', {});
const pages = nodes.map((graphQLNode) => {
// Use the node id to get the underlying node. It is not exactly the
// same node returned by GraphQL, because GraphQL resolvers might
// transform node fields.
const node = getNode(graphQLNode.id);
return {
url: node.fields.url,
relativePath: node.fields.relativePath,
relativeDir: node.fields.relativeDir,
base: node.fields.base,
name: node.fields.name,
frontmatter: node.frontmatter,
html: graphQLNode.html
};
});
nodes.forEach((graphQLNode) => {
const node = getNode(graphQLNode.id);
const url = node.fields.url;
const template = node.frontmatter.template;
if (!template) {
console.error(`Error: undefined template for ${url}`);
return;
}
const component = path.resolve(`./src/templates/${template}.js`);
if (!fs.existsSync(component)) {
console.error(`Error: component "src/templates/${template}.js" missing for ${url}`);
return;
}
const existingPageNode = _.get(sitePageNodesByPath, url);
const page = {
path: url,
component: component,
context: {
url: url,
relativePath: node.fields.relativePath,
relativeDir: node.fields.relativeDir,
base: node.fields.base,
name: node.fields.name,
frontmatter: node.frontmatter,
html: graphQLNode.html,
pages: pages,
site: {
siteMetadata: _.get(siteData, 'site-metadata', {}),
pathPrefix: siteNode.pathPrefix,
data: _.omit(siteData, 'site-metadata')
}
}
};
if (existingPageNode && !_.get(page, 'context.menus')) {
page.context.menus = _.get(existingPageNode, 'context.menus');
}
createPage(page);
});
});
};
```
</pre>
Gatsby Source Data
<pre>
```js
const path = require('path');
const yaml = require('js-yaml');
const fse = require('fs-extra');
const chokidar = require('chokidar');
const _ = require('lodash');
const metadataFileName = 'site-metadata.json';
const parsers = {
yaml: (data) => yaml.safeLoad(data, { schema: yaml.JSON_SCHEMA }),
json: (data) => JSON.parse(data)
};
const supportedExtensions = {
yaml: parsers.yaml,
yml: parsers.yaml,
json: parsers.json
};
exports.sourceNodes = (props, pluginOptions = {}) => {
const createContentDigest = props.createContentDigest;
const { createNode } = props.actions;
const reporter = props.reporter;
if (!_.get(pluginOptions, 'path')) {
pluginOptions.path = 'src/data';
}
if (!path.isAbsolute(pluginOptions.path)) {
pluginOptions.path = path.resolve(process.cwd(), pluginOptions.path);
}
reporter.info(`[gatsby-source-data] setup file watcher and create site data`);
const dataPath = pluginOptions.path;
const createSiteDataFromFilesPartial = _.partial(createSiteDataFromFiles, {
dataPath,
createNode,
createContentDigest,
reporter
});
const watcher = chokidar.watch([dataPath, metadataFileName], {
cwd: '.',
ignoreInitial: true
});
watcher.on('add', createSiteDataFromFilesPartial);
watcher.on('change', createSiteDataFromFilesPartial);
watcher.on('unlink', createSiteDataFromFilesPartial);
return createSiteDataFromFiles({ dataPath, createNode, createContentDigest, reporter }, null);
};
async function createSiteDataFromFiles({ dataPath, createNode, createContentDigest, reporter }, changedFile) {
reporter.info(`[gatsby-source-data] create site data from files, updated path: ${changedFile}`);
let dataFiles = [];
const dataPathExists = await fse.pathExists(dataPath);
if (dataPathExists) {
dataFiles = await readDirRecursively(dataPath);
}
const metadataPath = path.resolve(metadataFileName);
const metadataExists = await fse.pathExists(metadataPath);
if (metadataExists) {
dataFiles.push(metadataFileName);
}
const sortedDataFiles = dataFiles.slice().sort();
const data = await convertDataFilesToJSON(sortedDataFiles, dataPath, reporter);
createNode({
id: 'SiteData',
parent: null,
children: [],
data: data,
internal: {
type: 'SiteData',
contentDigest: createContentDigest(JSON.stringify(data)),
description: `Site data from ${path.relative(process.cwd(), dataPath)}`
}
});
}
async function readDirRecursively(dir, options) {
const rootDir = _.get(options, 'rootDir', dir);
const files = await fse.readdir(dir);
const promises = _.map(files, async (file) => {
const filePath = path.join(dir, file);
const stats = await fse.stat(filePath);
if (stats.isDirectory()) {
return readDirRecursively(filePath, { rootDir });
} else if (stats.isFile()) {
return path.relative(rootDir, filePath);
} else {
return null;
}
});
const recFiles = await Promise.all(promises);
return _.chain(recFiles).compact().flatten().value();
}
function convertDataFilesToJSON(dataFiles, dataDirPath, reporter) {
let promises = _.map(dataFiles, (filePath) => {
const pathObject = path.parse(filePath);
const absFilePath = pathObject.base === metadataFileName ? metadataFileName : path.join(dataDirPath, filePath);
const relPath = pathObject.base === metadataFileName ? metadataFileName : filePath;
const relDir = pathObject.base === metadataFileName ? '' : pathObject.dir;
const ext = pathObject.ext.substring(1);
if (!_.has(supportedExtensions, ext)) {
return null;
}
return fse.readFile(absFilePath).then((data) => {
const propPath = _.compact(relDir.split(path.sep).concat(pathObject.name));
const res = {};
try {
const parsedData = supportedExtensions[ext](data);
_.set(res, propPath, parsedData);
} catch (err) {
reporter.warn(`[gatsby-source-data] could not parse file: ${relPath}`);
}
return res;
});
});
return Promise.all(promises).then((results) => {
return _.reduce(results, (data, res) => _.merge(data, res), {});
});
}
</pre>