diff --git a/docs/blog/gatsbygram-case-study/index.md b/docs/blog/gatsbygram-case-study/index.md index 50f79b0504f29..d43ea49b2a8b5 100644 --- a/docs/blog/gatsbygram-case-study/index.md +++ b/docs/blog/gatsbygram-case-study/index.md @@ -164,18 +164,18 @@ the site's [`gatsby-node.js` file](https://github.com/gatsbyjs/gatsby/blob/1.0/examples/gatsbygram/gatsby-node.js): ```javascript -const _ = require("lodash") -const Promise = require("bluebird") -const path = require("path") -const slug = require("slug") -const slash = require("slash") +const _ = require(`lodash`) +const Promise = require(`bluebird`) +const path = require(`path`) +const slug = require(`slug`) +const slash = require(`slash`) // Implement the Gatsby API “createPages”. This is // called after the Gatsby bootstrap is finished so you have // access to any information necessary to programatically // create pages. -exports.createPages = ({ graphql, actionCreators }) => { - const { upsertPage } = actionCreators +exports.createPages = ({ graphql, boundActionCreators }) => { + const { upsertPage } = boundActionCreators return new Promise((resolve, reject) => { // The “graphql” function allows us to run arbitrary @@ -184,14 +184,15 @@ exports.createPages = ({ graphql, actionCreators }) => { // from static data that you can run queries against. // // Post is a data node type derived from data/posts.json - // which is created when scrapping Instagram. “allPosts” + // which is created when scrapping Instagram. “allPostsJson” // is a "connection" (a GraphQL convention for accessing // a list of nodes) gives us an easy way to query all // Post nodes. - graphql( - ` + resolve( + graphql( + ` { - allPosts(limit: 1000) { + allPostsJson(limit: 1000) { edges { node { id @@ -200,36 +201,37 @@ exports.createPages = ({ graphql, actionCreators }) => { } } ` - ).then(result => { - if (result.errors) { - console.log(result.errors) - reject(result.errors) - } + ).then(result => { + if (result.errors) { + reject(new Error(result.errors)) + } - // Create image post pages. - const postTemplate = path.resolve(`templates/post-page.js`) - // We want to create a detailed page for each - // Instagram post. Since the scrapped Instagram data - // already includes an ID field, we just use that for - // each page's path. - _.each(result.data.allPosts.edges, edge => { - // Gatsby uses Redux to manage its internal state. - // Plugins and sites can use functions like "upsertPage" - // to interact with Gatsby. - upsertPage({ - // Each page is required to have a `path` as well - // as a template component. The `context` is - // optional but is often necessary so the template - // can query data specific to each page. - path: slug(edge.node.id), - component: slash(postTemplate), - context: { - id: edge.node.id, - }, + // Create image post pages. + const postTemplate = path.resolve(`src/templates/post-page.js`) + // We want to create a detailed page for each + // Instagram post. Since the scrapped Instagram data + // already includes an ID field, we just use that for + // each page's path. + _.each(result.data.allPostsJson.edges, edge => { + // Gatsby uses Redux to manage its internal state. + // Plugins and sites can use functions like "upsertPage" + // to interact with Gatsby. + upsertPage({ + // Each page is required to have a `path` as well + // as a template component. The `context` is + // optional but is often necessary so the template + // can query data specific to each page. + path: `/${slug(edge.node.id)}/`, + component: slash(postTemplate), + context: { + id: edge.node.id, + }, + }) }) + + return }) - resolve() - }) + ) }) } ``` diff --git a/docs/docs/migrating-from-v0-to-v1.md b/docs/docs/migrating-from-v0-to-v1.md index 6e8b809b1e13e..f193d4524e756 100644 --- a/docs/docs/migrating-from-v0-to-v1.md +++ b/docs/docs/migrating-from-v0-to-v1.md @@ -120,15 +120,14 @@ module.exports = { It's handy to store the pathname of "slug" for each markdown page with the markdown data. This let's you easily query the slug from multiple places. -Here's how you do that (note, these APIs will be changing slightly soon but the -logic of your code will remain the same). +Here's how you do that. ```javascript // In your gatsby-node.js const path = require('path') exports.onNodeCreate = ({ node, boundActionCreators, getNode }) => { - const { updateNode } = boundActionCreators + const { addFieldToNode } = boundActionCreators let slug if (node.internal.type === `MarkdownRemark`) { const fileNode = getNode(node.parent) @@ -141,9 +140,8 @@ exports.onNodeCreate = ({ node, boundActionCreators, getNode }) => { slug = `/${parsedFilePath.dir}/` } - // Set the slug on the node and save the change. - node.slug = slug - updateNode(node) + // Add slug as a field on the node. + addFieldToNode({ node: node.id, fieldName: `slug`, fieldValue: slug }) } } ``` @@ -166,7 +164,9 @@ exports.createPages = ({ graphql, boundActionCreators }) => { allMarkdownRemark { edges { node { - slug + fields { + slug + } } } } @@ -181,10 +181,10 @@ exports.createPages = ({ graphql, boundActionCreators }) => { // Create blog posts pages. result.data.allMarkdownRemark.edges.forEach(edge => { upsertPage({ - path: edge.node.slug, // required + path: edge.node.fields.slug, // required component: blogPost, context: { - slug: edge.node.slug, + slug: edge.node.fields.slug, }, }) }) @@ -227,7 +227,7 @@ export default BlogPostTemplate export const pageQuery = graphql` query BlogPostBySlug($slug: String!) { - markdownRemark(slug: { eq: $slug }) { + markdownRemark(fields: { slug: { eq: $slug }}) { html frontmatter { title diff --git a/docs/docs/node-interface.md b/docs/docs/node-interface.md index e9fe6795b774e..2e013b957a043 100644 --- a/docs/docs/node-interface.md +++ b/docs/docs/node-interface.md @@ -11,11 +11,18 @@ The basic node data structure is as follows: id: String, children: Array[String], parent: String, +// Reserved for plugins who wish to extend other nodes. +fields: Object, internal: { contentDigest: String, mediaType: String, + // A globally unique node type choosen by the plugin owner. type: String, - pluginName: String, + // The plugin which created this node. + owner: String, + // Stores which plugins created which fields. + fieldOwners: Object, + // Raw content for this node. content: String, } ...other node type specific fields diff --git a/examples/gatsbygram/gatsby-node.js b/examples/gatsbygram/gatsby-node.js index f0f132c407427..4ee3c02ce9013 100644 --- a/examples/gatsbygram/gatsby-node.js +++ b/examples/gatsbygram/gatsby-node.js @@ -22,8 +22,9 @@ exports.createPages = ({ graphql, boundActionCreators }) => { // is a "connection" (a GraphQL convention for accessing // a list of nodes) gives us an easy way to query all // Post nodes. - graphql( - ` + resolve( + graphql( + ` { allPostsJson(limit: 1000) { edges { @@ -34,35 +35,36 @@ exports.createPages = ({ graphql, boundActionCreators }) => { } } ` - ).then(result => { - if (result.errors) { - reject(new Error(result.errors)) - } + ).then(result => { + if (result.errors) { + reject(new Error(result.errors)) + } - // Create image post pages. - const postTemplate = path.resolve(`src/templates/post-page.js`) - // We want to create a detailed page for each - // Instagram post. Since the scrapped Instagram data - // already includes an ID field, we just use that for - // each page's path. - _.each(result.data.allPostsJson.edges, edge => { - // Gatsby uses Redux to manage its internal state. - // Plugins and sites can use functions like "upsertPage" - // to interact with Gatsby. - upsertPage({ - // Each page is required to have a `path` as well - // as a template component. The `context` is - // optional but is often necessary so the template - // can query data specific to each page. - path: `/${slug(edge.node.id)}/`, - component: slash(postTemplate), - context: { - id: edge.node.id, - }, + // Create image post pages. + const postTemplate = path.resolve(`src/templates/post-page.js`) + // We want to create a detailed page for each + // Instagram post. Since the scrapped Instagram data + // already includes an ID field, we just use that for + // each page's path. + _.each(result.data.allPostsJson.edges, edge => { + // Gatsby uses Redux to manage its internal state. + // Plugins and sites can use functions like "upsertPage" + // to interact with Gatsby. + upsertPage({ + // Each page is required to have a `path` as well + // as a template component. The `context` is + // optional but is often necessary so the template + // can query data specific to each page. + path: `/${slug(edge.node.id)}/`, + component: slash(postTemplate), + context: { + id: edge.node.id, + }, + }) }) - }) - resolve() - }) + return + }) + ) }) } diff --git a/packages/gatsby-transformer-json/package.json b/packages/gatsby-transformer-json/package.json index 29d95fae57041..5ced889c81691 100644 --- a/packages/gatsby-transformer-json/package.json +++ b/packages/gatsby-transformer-json/package.json @@ -14,8 +14,7 @@ "author": "Kyle Mathews ", "license": "MIT", "dependencies": { - "bluebird": "^3.5.0", - "unist-util-select": "^1.5.0" + "bluebird": "^3.5.0" }, "devDependencies": { "babel-cli": "^6.24.1" diff --git a/packages/gatsby-transformer-json/src/__tests__/__snapshots__/gatsby-node.js.snap b/packages/gatsby-transformer-json/src/__tests__/__snapshots__/gatsby-node.js.snap index 92cad30106b90..ecae73a606774 100644 --- a/packages/gatsby-transformer-json/src/__tests__/__snapshots__/gatsby-node.js.snap +++ b/packages/gatsby-transformer-json/src/__tests__/__snapshots__/gatsby-node.js.snap @@ -39,18 +39,58 @@ exports[`Process JSON nodes correctly correctly creates nodes from JSON which is Array [ Array [ Object { - "children": Array [ - "foo", - "whatever [1] >>> JSON", - ], - "content": "[{\\"id\\":\\"foo\\",\\"blue\\":true,\\"funny\\":\\"yup\\"},{\\"blue\\":false,\\"funny\\":\\"nope\\"}]", - "id": "whatever", - "internal": Object { - "contentDigest": "whatever", - "mediaType": "application/json", - "name": "test", + "child": Object { + "blue": true, + "children": Array [], + "funny": "yup", + "id": "foo", + "internal": Object { + "content": "{\\"id\\":\\"foo\\",\\"blue\\":true,\\"funny\\":\\"yup\\"}", + "contentDigest": "8838e569ae02d98806532310fb2a577a", + "mediaType": "application/json", + "type": "UndefinedJson", + }, + "parent": "whatever", + }, + "parent": Object { + "children": Array [], + "content": "[{\\"id\\":\\"foo\\",\\"blue\\":true,\\"funny\\":\\"yup\\"},{\\"blue\\":false,\\"funny\\":\\"nope\\"}]", + "id": "whatever", + "internal": Object { + "contentDigest": "whatever", + "mediaType": "application/json", + "name": "test", + }, + "parent": "SOURCE", + }, + }, + ], + Array [ + Object { + "child": Object { + "blue": false, + "children": Array [], + "funny": "nope", + "id": "whatever [1] >>> JSON", + "internal": Object { + "content": "{\\"blue\\":false,\\"funny\\":\\"nope\\"}", + "contentDigest": "f624311d932d73dcd416d2a8bea2b67d", + "mediaType": "application/json", + "type": "UndefinedJson", + }, + "parent": "whatever", + }, + "parent": Object { + "children": Array [], + "content": "[{\\"id\\":\\"foo\\",\\"blue\\":true,\\"funny\\":\\"yup\\"},{\\"blue\\":false,\\"funny\\":\\"nope\\"}]", + "id": "whatever", + "internal": Object { + "contentDigest": "whatever", + "mediaType": "application/json", + "name": "test", + }, + "parent": "SOURCE", }, - "parent": "SOURCE", }, ], ] diff --git a/packages/gatsby-transformer-json/src/__tests__/gatsby-node.js b/packages/gatsby-transformer-json/src/__tests__/gatsby-node.js index 6a2e18aa81f84..9fc819815e421 100644 --- a/packages/gatsby-transformer-json/src/__tests__/gatsby-node.js +++ b/packages/gatsby-transformer-json/src/__tests__/gatsby-node.js @@ -26,8 +26,8 @@ describe(`Process JSON nodes correctly`, () => { node.content = JSON.stringify(data) const createNode = jest.fn() - const updateNode = jest.fn() - const boundActionCreators = { createNode, updateNode } + const addNodeToParent = jest.fn() + const boundActionCreators = { createNode, addNodeToParent } await onNodeCreate({ node, @@ -35,9 +35,9 @@ describe(`Process JSON nodes correctly`, () => { boundActionCreators, }).then(() => { expect(createNode.mock.calls).toMatchSnapshot() - expect(updateNode.mock.calls).toMatchSnapshot() + expect(addNodeToParent.mock.calls).toMatchSnapshot() expect(createNode).toHaveBeenCalledTimes(2) - expect(updateNode).toHaveBeenCalledTimes(1) + expect(addNodeToParent).toHaveBeenCalledTimes(2) }) }) @@ -49,8 +49,8 @@ describe(`Process JSON nodes correctly`, () => { node.content = JSON.stringify(data) const createNode = jest.fn() - const updateNode = jest.fn() - const boundActionCreators = { createNode, updateNode } + const addNodeToParent = jest.fn() + const boundActionCreators = { createNode, addNodeToParent } await onNodeCreate({ node, @@ -71,8 +71,8 @@ describe(`Process JSON nodes correctly`, () => { node.content = JSON.stringify(data) const createNode = jest.fn() - const updateNode = jest.fn() - const boundActionCreators = { createNode, updateNode } + const addNodeToParent = jest.fn() + const boundActionCreators = { createNode, addNodeToParent } await onNodeCreate({ node, diff --git a/packages/gatsby-transformer-json/src/gatsby-node.js b/packages/gatsby-transformer-json/src/gatsby-node.js index b8b8f17090bdf..11071e2c0e9d6 100644 --- a/packages/gatsby-transformer-json/src/gatsby-node.js +++ b/packages/gatsby-transformer-json/src/gatsby-node.js @@ -1,17 +1,16 @@ -const select = require(`unist-util-select`) const Promise = require(`bluebird`) const fs = require(`fs`) const _ = require(`lodash`) const crypto = require(`crypto`) async function onNodeCreate({ node, boundActionCreators, loadNodeContent }) { - const { createNode, updateNode } = boundActionCreators + const { createNode, addNodeToParent } = boundActionCreators // Don't reprocess our own nodes! (note: this doesn't normally happen // but since this transformer creates new nodes with the same media-type // as its parent node, we have to add this check that we didn't create // the node). - if (node.internal.pluginName === `gatsby-transformer-json`) { + if (node.internal.owner === `gatsby-transformer-json`) { return } @@ -49,9 +48,10 @@ async function onNodeCreate({ node, boundActionCreators, loadNodeContent }) { } }) - node.children = node.children.concat(JSONArray.map(n => n.id)) - updateNode(node) - _.each(JSONArray, j => createNode(j)) + _.each(JSONArray, j => { + createNode(j) + addNodeToParent({ parent: node, child: j }) + }) } return diff --git a/packages/gatsby-transformer-react-docgen/src/__tests__/on-node-create.js b/packages/gatsby-transformer-react-docgen/src/__tests__/on-node-create.js index ce2e8cc200216..8ebd78f52653a 100644 --- a/packages/gatsby-transformer-react-docgen/src/__tests__/on-node-create.js +++ b/packages/gatsby-transformer-react-docgen/src/__tests__/on-node-create.js @@ -38,7 +38,7 @@ describe(`transformer-react-doc-gen: onNodeCreate`, () => { loadNodeContent = jest.fn(node => readFile(node.__fixture)) boundActionCreators = { createNode: jest.fn(n => createdNodes.push(n)), - updateNode: jest.fn(n => updatedNodes.push(n)), + addNodeToParent: jest.fn(n => updatedNodes.push(n)), } }) diff --git a/packages/gatsby-transformer-react-docgen/src/on-node-create.js b/packages/gatsby-transformer-react-docgen/src/on-node-create.js index aac5396b21a48..8bd441d49266c 100644 --- a/packages/gatsby-transformer-react-docgen/src/on-node-create.js +++ b/packages/gatsby-transformer-react-docgen/src/on-node-create.js @@ -7,8 +7,8 @@ const propsId = (parentId, name) => `${parentId}--ComponentProp-${name}` const descId = parentId => `${parentId}--ComponentDescription` function createDescriptionNode(node, entry, boundActionCreators) { - if (!entry.description) return - const { createNode, updateNode } = boundActionCreators + if (!entry.description) return node + const { createNode } = boundActionCreators const descriptionNode = { id: descId(node.id), @@ -25,8 +25,9 @@ function createDescriptionNode(node, entry, boundActionCreators) { node.description___NODE = descriptionNode.id node.children = node.children.concat([descriptionNode.id]) - updateNode(node) createNode(descriptionNode) + + return node } function createPropNodes(node, component, boundActionCreators) { @@ -37,7 +38,7 @@ function createPropNodes(node, component, boundActionCreators) { let propNodeId = propsId(node.id, prop.name) let content = JSON.stringify(prop) - const propNode = { + let propNode = { ...prop, id: propNodeId, children: [], @@ -51,20 +52,20 @@ function createPropNodes(node, component, boundActionCreators) { }, } children[i] = propNode.id + propNode = createDescriptionNode(propNode, prop, boundActionCreators) createNode(propNode) - createDescriptionNode(propNode, prop, boundActionCreators) }) node.props___NODE = children node.children = node.children.concat(children) - updateNode(node) + return node } export default function onNodeCreate( { node, loadNodeContent, boundActionCreators }, pluginOptions ) { - const { createNode, updateNode } = boundActionCreators + const { createNode, addNodeToParent } = boundActionCreators if (node.internal.mediaType !== `application/javascript`) return null @@ -77,7 +78,7 @@ export default function onNodeCreate( const contentDigest = digest(strContent) const nodeId = `${node.id}--${component.displayName}--ComponentMetadata` - const metadataNode = { + let metadataNode = { ...component, props: null, // handled by the prop node creation id: nodeId, @@ -91,11 +92,18 @@ export default function onNodeCreate( }, } - node.children = node.children.concat([metadataNode.id]) - updateNode(node) + addNodeToParent({ parent: node, child: metadataNode }) + metadataNode = createPropNodes( + metadataNode, + component, + boundActionCreators + ) + metadataNode = createDescriptionNode( + metadataNode, + component, + boundActionCreators + ) createNode(metadataNode) - createPropNodes(metadataNode, component, boundActionCreators) - createDescriptionNode(metadataNode, component, boundActionCreators) }) }) .catch(err => console.log(err)) diff --git a/packages/gatsby-transformer-remark/src/__tests__/__snapshots__/gatsby-node.js.snap b/packages/gatsby-transformer-remark/src/__tests__/__snapshots__/gatsby-node.js.snap index b0ec1c5966eb4..056717ea13389 100644 --- a/packages/gatsby-transformer-remark/src/__tests__/__snapshots__/gatsby-node.js.snap +++ b/packages/gatsby-transformer-remark/src/__tests__/__snapshots__/gatsby-node.js.snap @@ -29,20 +29,38 @@ exports[`Process markdown content correctly Correctly creates a new MarkdownRema Array [ Array [ Object { - "children": Array [ - "whatever >>> MarkdownRemark", - ], - "content": "--- + "child": Object { + "children": Array [], + "frontmatter": Object { + "date": "12/12/2014", + "parent": "whatever", + "title": "my little pony", + }, + "id": "whatever >>> MarkdownRemark", + "internal": Object { + "content": " +Where oh where is my little pony? + ", + "contentDigest": "54cfa2ad99756f2fa232cac585280a37", + "mediaType": "text/x-markdown", + "type": "MarkdownRemark", + }, + "parent": "whatever", + }, + "parent": Object { + "children": Array [], + "content": "--- title: \\"my little pony\\" date: \\"12/12/2014\\" --- Where oh where is my little pony? ", - "id": "whatever", - "internal": Object { - "contentDigest": "whatever", - "mediaType": "text/x-markdown", + "id": "whatever", + "internal": Object { + "contentDigest": "whatever", + "mediaType": "text/x-markdown", + }, }, }, ], diff --git a/packages/gatsby-transformer-remark/src/__tests__/gatsby-node.js b/packages/gatsby-transformer-remark/src/__tests__/gatsby-node.js index f8d91e0e7ba75..3bf78205dc1f8 100644 --- a/packages/gatsby-transformer-remark/src/__tests__/gatsby-node.js +++ b/packages/gatsby-transformer-remark/src/__tests__/gatsby-node.js @@ -26,8 +26,8 @@ Where oh where is my little pony? node.content = content const createNode = jest.fn() - const updateNode = jest.fn() - const boundActionCreators = { createNode, updateNode } + const addNodeToParent = jest.fn() + const boundActionCreators = { createNode, addNodeToParent } await onNodeCreate({ node, @@ -35,9 +35,9 @@ Where oh where is my little pony? boundActionCreators, }).then(() => { expect(createNode.mock.calls).toMatchSnapshot() - expect(updateNode.mock.calls).toMatchSnapshot() + expect(addNodeToParent.mock.calls).toMatchSnapshot() expect(createNode).toHaveBeenCalledTimes(1) - expect(updateNode).toHaveBeenCalledTimes(1) + expect(addNodeToParent).toHaveBeenCalledTimes(1) }) }) }) diff --git a/packages/gatsby-transformer-remark/src/on-node-create.js b/packages/gatsby-transformer-remark/src/on-node-create.js index 98bf755693bca..ef52252a1008a 100644 --- a/packages/gatsby-transformer-remark/src/on-node-create.js +++ b/packages/gatsby-transformer-remark/src/on-node-create.js @@ -7,7 +7,7 @@ module.exports = async function onNodeCreate({ loadNodeContent, boundActionCreators, }) { - const { createNode, updateNode } = boundActionCreators + const { createNode, addNodeToParent } = boundActionCreators // Don't reprocess our own nodes! (note: this doesn't normally happen // but since this transformer creates new nodes with the same media-type @@ -50,7 +50,6 @@ module.exports = async function onNodeCreate({ markdownNode.fileAbsolutePath = node.absolutePath } - node.children = node.children.concat([markdownNode.id]) - updateNode(node) createNode(markdownNode) + addNodeToParent({ parent: node, child: markdownNode }) } diff --git a/packages/gatsby-transformer-sharp/src/__tests__/__snapshots__/gatsby-node.js.snap b/packages/gatsby-transformer-sharp/src/__tests__/__snapshots__/gatsby-node.js.snap index 82133ebe8cb54..5ed6aecbf3c1c 100644 --- a/packages/gatsby-transformer-sharp/src/__tests__/__snapshots__/gatsby-node.js.snap +++ b/packages/gatsby-transformer-sharp/src/__tests__/__snapshots__/gatsby-node.js.snap @@ -21,13 +21,23 @@ exports[`Process image nodes correctly correctly creates an ImageSharp node from Array [ Array [ Object { - "children": Array [ - "whatever >> ImageSharp", - ], - "extension": "png", - "id": "whatever", - "internal": Object { - "contentDigest": "whatever", + "child": Object { + "children": Array [], + "id": "whatever >> ImageSharp", + "internal": Object { + "contentDigest": "whatever", + "mediaType": undefined, + "type": "ImageSharp", + }, + "parent": "whatever", + }, + "parent": Object { + "children": Array [], + "extension": "png", + "id": "whatever", + "internal": Object { + "contentDigest": "whatever", + }, }, }, ], diff --git a/packages/gatsby-transformer-sharp/src/__tests__/gatsby-node.js b/packages/gatsby-transformer-sharp/src/__tests__/gatsby-node.js index 8b470f5ed735f..61cdf05cb4203 100644 --- a/packages/gatsby-transformer-sharp/src/__tests__/gatsby-node.js +++ b/packages/gatsby-transformer-sharp/src/__tests__/gatsby-node.js @@ -13,17 +13,17 @@ describe(`Process image nodes correctly`, () => { }, } const createNode = jest.fn() - const updateNode = jest.fn() - const boundActionCreators = { createNode, updateNode } + const addNodeToParent = jest.fn() + const boundActionCreators = { createNode, addNodeToParent } await onNodeCreate({ node, boundActionCreators, }).then(() => { expect(createNode.mock.calls).toMatchSnapshot() - expect(updateNode.mock.calls).toMatchSnapshot() + expect(addNodeToParent.mock.calls).toMatchSnapshot() expect(createNode).toHaveBeenCalledTimes(1) - expect(updateNode).toHaveBeenCalledTimes(1) + expect(addNodeToParent).toHaveBeenCalledTimes(1) }) }) }) diff --git a/packages/gatsby-transformer-sharp/src/on-node-create.js b/packages/gatsby-transformer-sharp/src/on-node-create.js index 24fd2527064ea..2addb8dea15e7 100644 --- a/packages/gatsby-transformer-sharp/src/on-node-create.js +++ b/packages/gatsby-transformer-sharp/src/on-node-create.js @@ -1,7 +1,7 @@ const _ = require(`lodash`) module.exports = async function onNodeCreate({ node, boundActionCreators }) { - const { createNode, updateNode } = boundActionCreators + const { createNode, addNodeToParent } = boundActionCreators const extensions = [`jpeg`, `jpg`, `png`, `webp`, `tif`, `tiff`, `svg`] if (!_.includes(extensions, node.extension)) { @@ -19,9 +19,8 @@ module.exports = async function onNodeCreate({ node, boundActionCreators }) { }, } - node.children = node.children.concat([imageNode.id]) - updateNode(node) createNode(imageNode) + addNodeToParent({ parent: node, child: imageNode }) return } diff --git a/packages/gatsby-transformer-yaml/package.json b/packages/gatsby-transformer-yaml/package.json index 18778666e156b..00fe9c04c1cd6 100644 --- a/packages/gatsby-transformer-yaml/package.json +++ b/packages/gatsby-transformer-yaml/package.json @@ -15,8 +15,7 @@ "license": "MIT", "dependencies": { "bluebird": "^3.5.0", - "js-yaml": "3.7.0", - "unist-util-select": "^1.5.0" + "js-yaml": "3.7.0" }, "devDependencies": { "babel-cli": "^6.24.1" diff --git a/packages/gatsby-transformer-yaml/src/__tests__/__snapshots__/gatsby-node.js.snap b/packages/gatsby-transformer-yaml/src/__tests__/__snapshots__/gatsby-node.js.snap index d5d6103ceb320..d8578b1d38195 100644 --- a/packages/gatsby-transformer-yaml/src/__tests__/__snapshots__/gatsby-node.js.snap +++ b/packages/gatsby-transformer-yaml/src/__tests__/__snapshots__/gatsby-node.js.snap @@ -39,22 +39,66 @@ exports[`Process YAML nodes correctly correctly creates nodes from JSON which is Array [ Array [ Object { - "children": Array [ - "whatever [0] >>> YAML", - "whatever [1] >>> YAML", - ], - "content": "- blue: true + "child": Object { + "blue": true, + "children": Array [], + "funny": "yup", + "id": "whatever [0] >>> YAML", + "internal": Object { + "content": "{\\"blue\\":true,\\"funny\\":\\"yup\\"}", + "contentDigest": "73901821b17d5aa9dd6026181f73b64c", + "mediaType": "application/json", + "type": "TestYaml", + }, + "parent": "whatever", + }, + "parent": Object { + "children": Array [], + "content": "- blue: true funny: yup - blue: false funny: nope ", - "id": "whatever", - "internal": Object { - "contentDigest": "whatever", - "mediaType": "text/yaml", + "id": "whatever", + "internal": Object { + "contentDigest": "whatever", + "mediaType": "text/yaml", + }, + "name": "test", + "parent": "SOURCE", + }, + }, + ], + Array [ + Object { + "child": Object { + "blue": false, + "children": Array [], + "funny": "nope", + "id": "whatever [1] >>> YAML", + "internal": Object { + "content": "{\\"blue\\":false,\\"funny\\":\\"nope\\"}", + "contentDigest": "f624311d932d73dcd416d2a8bea2b67d", + "mediaType": "application/json", + "type": "TestYaml", + }, + "parent": "whatever", + }, + "parent": Object { + "children": Array [], + "content": "- blue: true + funny: yup +- blue: false + funny: nope +", + "id": "whatever", + "internal": Object { + "contentDigest": "whatever", + "mediaType": "text/yaml", + }, + "name": "test", + "parent": "SOURCE", }, - "name": "test", - "parent": "SOURCE", }, ], ] diff --git a/packages/gatsby-transformer-yaml/src/__tests__/gatsby-node.js b/packages/gatsby-transformer-yaml/src/__tests__/gatsby-node.js index 32ab132c2ab4e..f99fdc7b772be 100644 --- a/packages/gatsby-transformer-yaml/src/__tests__/gatsby-node.js +++ b/packages/gatsby-transformer-yaml/src/__tests__/gatsby-node.js @@ -24,8 +24,8 @@ describe(`Process YAML nodes correctly`, () => { node.content = yaml.safeDump(data) const createNode = jest.fn() - const updateNode = jest.fn() - const boundActionCreators = { createNode, updateNode } + const addNodeToParent = jest.fn() + const boundActionCreators = { createNode, addNodeToParent } await onNodeCreate({ node, @@ -33,9 +33,9 @@ describe(`Process YAML nodes correctly`, () => { boundActionCreators, }).then(() => { expect(createNode.mock.calls).toMatchSnapshot() - expect(updateNode.mock.calls).toMatchSnapshot() + expect(addNodeToParent.mock.calls).toMatchSnapshot() expect(createNode).toHaveBeenCalledTimes(2) - expect(updateNode).toHaveBeenCalledTimes(1) + expect(addNodeToParent).toHaveBeenCalledTimes(2) }) }) @@ -47,8 +47,8 @@ describe(`Process YAML nodes correctly`, () => { node.content = yaml.safeDump(data) const createNode = jest.fn() - const updateNode = jest.fn() - const boundActionCreators = { createNode, updateNode } + const addNodeToParent = jest.fn() + const boundActionCreators = { createNode, addNodeToParent } await onNodeCreate({ node, @@ -69,8 +69,8 @@ describe(`Process YAML nodes correctly`, () => { node.content = yaml.safeDump(data) const createNode = jest.fn() - const updateNode = jest.fn() - const boundActionCreators = { createNode, updateNode } + const addNodeToParent = jest.fn() + const boundActionCreators = { createNode, addNodeToParent } await onNodeCreate({ node, diff --git a/packages/gatsby-transformer-yaml/src/gatsby-node.js b/packages/gatsby-transformer-yaml/src/gatsby-node.js index 7df52cb13cce4..e7566caf0220e 100644 --- a/packages/gatsby-transformer-yaml/src/gatsby-node.js +++ b/packages/gatsby-transformer-yaml/src/gatsby-node.js @@ -6,7 +6,7 @@ const _ = require(`lodash`) const crypto = require(`crypto`) async function onNodeCreate({ node, boundActionCreators, loadNodeContent }) { - const { createNode, updateNode } = boundActionCreators + const { createNode, addNodeToParent } = boundActionCreators if (node.internal.mediaType !== `text/yaml`) { return } @@ -40,9 +40,10 @@ async function onNodeCreate({ node, boundActionCreators, loadNodeContent }) { } }) - node.children = node.children.concat(yamlArray.map(y => y.id)) - updateNode(node) - _.each(yamlArray, y => createNode(y)) + _.each(yamlArray, y => { + createNode(y) + addNodeToParent({ parent: node, child: y }) + }) } return diff --git a/packages/gatsby/src/joi-schemas/joi.js b/packages/gatsby/src/joi-schemas/joi.js index 46a09f295eee9..7d4fa95472bd4 100644 --- a/packages/gatsby/src/joi-schemas/joi.js +++ b/packages/gatsby/src/joi-schemas/joi.js @@ -26,11 +26,13 @@ export const nodeSchema = Joi.object() id: Joi.string().required(), children: Joi.array(Joi.string()).required(), parent: Joi.string().required(), + fields: Joi.object(), internal: Joi.object().keys({ contentDigest: Joi.string().required(), mediaType: Joi.string().required(), type: Joi.string().required(), - pluginName: Joi.string().required(), + owner: Joi.string().required(), + fieldOwners: Joi.array(), content: Joi.string(), }), }) diff --git a/packages/gatsby/src/redux/__tests__/__snapshots__/nodes.js.snap b/packages/gatsby/src/redux/__tests__/__snapshots__/nodes.js.snap index 050d370fe8ed7..9e45cd81fa9af 100644 --- a/packages/gatsby/src/redux/__tests__/__snapshots__/nodes.js.snap +++ b/packages/gatsby/src/redux/__tests__/__snapshots__/nodes.js.snap @@ -1,18 +1,45 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Create and update nodes allows adding fields to nodes 1`] = ` +Object { + "hi": Object { + "children": Array [], + "fields": Object { + "joy": "soul's delight", + }, + "id": "hi", + "internal": Object { + "contentDigest": "hasdfljds", + "fieldOwners": Object { + "joy": "test", + }, + "mediaType": "test", + "owner": "tests", + "type": "Test", + }, + "parent": "test", + "pickle": true, + }, +} +`; + exports[`Create and update nodes allows creating nodes 1`] = ` Object { "payload": Object { "children": Array [], - "contentDigest": "hasdfljds", "id": "hi", - "mediaType": "test", + "internal": Object { + "contentDigest": "hasdfljds", + "mediaType": "test", + "owner": "tests", + "type": "Test", + }, "parent": "test", "pickle": true, - "pluginName": "tests", - "type": "Test", }, - "plugin": "", + "plugin": Object { + "name": "tests", + }, "type": "CREATE_NODE", } `; @@ -21,13 +48,86 @@ exports[`Create and update nodes allows creating nodes 2`] = ` Object { "hi": Object { "children": Array [], - "contentDigest": "hasdfljds", "id": "hi", - "mediaType": "test", + "internal": Object { + "contentDigest": "hasdfljds", + "mediaType": "test", + "owner": "tests", + "type": "Test", + }, "parent": "test", "pickle": true, - "pluginName": "tests", - "type": "Test", }, } `; + +exports[`Create and update nodes throws error if a field is updated by a plugin not its owner 1`] = ` +"A plugin tried to update a node field that it doesn't own: + +Node id: hi +Plugin: test2 +fieldName: joy +fieldValue: soul's delight" +`; + +exports[`Create and update nodes throws error if a node is created by a plugin not its owner 1`] = ` +"The plugin \\"pluginB\\" created a node of a type owned by another plugin. + + The node type \\"mineOnly\\" is owned by \\"pluginA\\". + + If you copy and pasted code from elsewhere, you'll need to pick a new type name + for your new node(s). + + The node object passed to \\"createNode\\": + + { + \\"id\\": \\"hi2\\", + \\"children\\": [], + \\"parent\\": \\"test\\", + \\"internal\\": { + \\"contentDigest\\": \\"hasdfljds\\", + \\"mediaType\\": \\"test\\", + \\"owner\\": \\"pluginB\\", + \\"type\\": \\"mineOnly\\" + }, + \\"pickle\\": true +} + + The plugin creating the node: + + { + \\"name\\": \\"pluginB\\" +}" +`; + +exports[`Create and update nodes throws error if a node sets a value on "fields" 1`] = ` +"Plugins creating nodes can not set data on the reserved field \\"fields\\" + as this is reserved for plugins which wish to extend your nodes. + + If your plugin didn't add \\"fields\\" you're probably seeing this + error because you're reusing an old node object. + + Node: + + { + \\"id\\": \\"hi\\", + \\"children\\": [], + \\"parent\\": \\"test\\", + \\"fields\\": { + \\"test\\": \\"I can't do this but I like to test boundaries\\" + }, + \\"internal\\": { + \\"contentDigest\\": \\"hasdfljds\\", + \\"mediaType\\": \\"test\\", + \\"owner\\": \\"pluginA\\", + \\"type\\": \\"mineOnly\\" + }, + \\"pickle\\": true +} + + Plugin that created the node: + + { + \\"name\\": \\"pluginA\\" +}" +`; diff --git a/packages/gatsby/src/redux/__tests__/nodes.js b/packages/gatsby/src/redux/__tests__/nodes.js index c0c554ea9c427..bd6990c6a2426 100644 --- a/packages/gatsby/src/redux/__tests__/nodes.js +++ b/packages/gatsby/src/redux/__tests__/nodes.js @@ -3,67 +3,66 @@ const nodeReducer = require(`../reducers/nodes`) const nodeTouchedReducer = require(`../reducers/nodes-touched`) describe(`Create and update nodes`, () => { - // TODO add these back when we stop directly consoleing errors. - // Right now makes tests noisy. - // - // it(`validates created nodes`, () => { - // const action = actions.createNode({ - // type: `Test`, - // }); - // expect(action.type).toEqual(`VALIDATION_ERROR`); - // }); - // it(`validates updated nodes`, () => { - // const action = actions.updateNode({ - // type: `Test`, - // }); - // expect(action.type).toEqual(`VALIDATION_ERROR`); - // }); - it(`allows creating nodes`, () => { - const action = actions.createNode({ - id: `hi`, - contentDigest: `hasdfljds`, - children: [], - parent: `test`, - mediaType: `test`, - pluginName: `tests`, - type: `Test`, - pickle: true, - }) + const action = actions.createNode( + { + id: `hi`, + children: [], + parent: `test`, + internal: { + contentDigest: `hasdfljds`, + mediaType: `test`, + owner: `tests`, + type: `Test`, + }, + pickle: true, + }, + { name: `tests` } + ) expect(action).toMatchSnapshot() expect(nodeReducer(undefined, action)).toMatchSnapshot() }) it(`allows updating nodes`, () => { - const action = actions.createNode({ - id: `hi`, - contentDigest: `hasdfljds`, - children: [], - parent: `test`, - mediaType: `test`, - pluginName: `tests`, - type: `Test`, - pickle: true, - deep: { - array: [0, 1, { boom: true }], + const action = actions.createNode( + { + id: `hi`, + children: [], + parent: `test`, + internal: { + contentDigest: `hasdfljds`, + mediaType: `test`, + owner: `tests`, + type: `Test`, + }, + pickle: true, + deep: { + array: [0, 1, { boom: true }], + }, }, - }) - const updateAction = actions.createNode({ - id: `hi`, - contentDigest: `hasdfljds`, - children: [], - parent: `test`, - mediaType: `test`, - pluginName: `tests`, - type: `Test`, - pickle: false, - deep: { - array: [1, 2], + { name: `tests` } + ) + const updateAction = actions.createNode( + { + id: `hi`, + children: [], + parent: `test`, + internal: { + contentDigest: `hasdfljds`, + mediaType: `test`, + owner: `tests`, + type: `Test`, + }, + pickle: false, + deep: { + array: [1, 2], + }, + deep2: { + boom: `foo`, + }, }, - deep2: { - boom: `foo`, - }, - }) + { name: `tests` } + ) let state = nodeReducer(undefined, action) state = nodeReducer(state, updateAction) expect(state[`hi`].pickle).toEqual(false) @@ -72,17 +71,156 @@ describe(`Create and update nodes`, () => { }) it(`nodes that are added are also "touched"`, () => { - const action = actions.createNode({ - id: `hi`, - contentDigest: `hasdfljds`, - children: [], - parent: `test`, - mediaType: `test`, - pluginName: `tests`, - type: `Test`, - pickle: true, - }) + const action = actions.createNode( + { + id: `hi`, + children: [], + parent: `test`, + internal: { + contentDigest: `hasdfljds`, + mediaType: `test`, + owner: `tests`, + type: `Test`, + }, + pickle: true, + }, + { name: `tests` } + ) let state = nodeTouchedReducer(undefined, action) expect(state[`hi`]).toBe(true) }) + + it(`allows adding fields to nodes`, () => { + const action = actions.createNode( + { + id: `hi`, + children: [], + parent: `test`, + internal: { + contentDigest: `hasdfljds`, + mediaType: `test`, + owner: `tests`, + type: `Test`, + }, + pickle: true, + }, + { name: `tests` } + ) + let state = nodeReducer(undefined, action) + + const addFieldAction = actions.addFieldToNode( + { + node: state[`hi`], + fieldName: `joy`, + fieldValue: `soul's delight`, + }, + { name: `test` } + ) + state = nodeReducer(state, addFieldAction) + expect(state).toMatchSnapshot() + }) + + it(`throws error if a field is updated by a plugin not its owner`, () => { + const action = actions.createNode( + { + id: `hi`, + children: [], + parent: `test`, + internal: { + contentDigest: `hasdfljds`, + mediaType: `test`, + owner: `tests`, + type: `Test`, + }, + pickle: true, + }, + { name: `tests` } + ) + let state = nodeReducer(undefined, action) + + const addFieldAction = actions.addFieldToNode( + { + node: state[`hi`], + fieldName: `joy`, + fieldValue: `soul's delight`, + }, + { name: `test` } + ) + state = nodeReducer(state, addFieldAction) + + function callActionCreator() { + actions.addFieldToNode( + { + node: state[`hi`], + fieldName: `joy`, + fieldValue: `soul's delight`, + }, + { name: `test2` } + ) + } + expect(callActionCreator).toThrowErrorMatchingSnapshot() + }) + + it(`throws error if a node is created by a plugin not its owner`, () => { + actions.createNode( + { + id: `hi`, + children: [], + parent: `test`, + internal: { + contentDigest: `hasdfljds`, + mediaType: `test`, + owner: `pluginA`, + type: `mineOnly`, + }, + pickle: true, + }, + { name: `pluginA` } + ) + + function callActionCreator() { + actions.createNode( + { + id: `hi2`, + children: [], + parent: `test`, + internal: { + contentDigest: `hasdfljds`, + mediaType: `test`, + owner: `pluginB`, + type: `mineOnly`, + }, + pickle: true, + }, + { name: `pluginB` } + ) + } + + expect(callActionCreator).toThrowErrorMatchingSnapshot() + }) + + it(`throws error if a node sets a value on "fields"`, () => { + function callActionCreator() { + actions.createNode( + { + id: `hi`, + children: [], + parent: `test`, + fields: { + test: `I can't do this but I like to test boundaries`, + }, + internal: { + contentDigest: `hasdfljds`, + mediaType: `test`, + owner: `pluginA`, + type: `mineOnly`, + }, + pickle: true, + }, + { name: `pluginA` } + ) + } + + expect(callActionCreator).toThrowErrorMatchingSnapshot() + }) }) diff --git a/packages/gatsby/src/redux/actions.js b/packages/gatsby/src/redux/actions.js index 172b5c3690075..c0e3abfd43836 100644 --- a/packages/gatsby/src/redux/actions.js +++ b/packages/gatsby/src/redux/actions.js @@ -2,6 +2,7 @@ import Joi from "joi" import chalk from "chalk" const _ = require(`lodash`) const { bindActionCreators } = require(`redux`) +const { stripIndent } = require(`common-tags`) const { getNode, hasNodeChanged } = require(`./index`) @@ -52,29 +53,6 @@ actions.upsertPage = (page, plugin = ``) => { } } -actions.updateNode = (node, plugin = ``) => { - if (!_.isObject(node)) { - return console.log( - chalk.bold.red( - `The node passed to the "updateNode" action creator must be an object` - ) - ) - } - const result = Joi.validate(node, joiSchemas.nodeSchema) - if (result.error) { - console.log(chalk.bold.red(`The updated node didn't pass validation`)) - console.log(chalk.bold.red(result.error)) - console.log(node) - return { type: `VALIDATION_ERROR`, error: true } - } - - return { - type: `UPDATE_NODE`, - plugin, - payload: node, - } -} - actions.deleteNode = (nodeId, plugin = ``) => { return { type: `DELETE_NODE`, @@ -99,7 +77,8 @@ actions.touchNode = (nodeId, plugin = ``) => { } } -actions.createNode = (node, plugin = ``) => { +const typeOwners = {} +actions.createNode = (node, plugin) => { if (!_.isObject(node)) { return console.log( chalk.bold.red( @@ -107,6 +86,17 @@ actions.createNode = (node, plugin = ``) => { ) ) } + + // Ensure the new node has an internals object. + if (!node.internal) { + node.internal = {} + } + + // Add the plugin name to the internal object. + if (plugin) { + node.internal.owner = plugin.name + } + const result = Joi.validate(node, joiSchemas.nodeSchema) if (result.error) { console.log(chalk.bold.red(`The new node didn't pass validation`)) @@ -115,11 +105,71 @@ actions.createNode = (node, plugin = ``) => { return { type: `VALIDATION_ERROR`, error: true } } + // Ensure node isn't directly setting fields. + if (node.fields) { + throw new Error( + stripIndent` + Plugins creating nodes can not set data on the reserved field "fields" + as this is reserved for plugins which wish to extend your nodes. + + If your plugin didn't add "fields" you're probably seeing this + error because you're reusing an old node object. + + Node: + + ${JSON.stringify(node, null, 4)} + + Plugin that created the node: + + ${JSON.stringify(plugin, null, 4)} + ` + ) + } + + // Ensure the plugin isn't creating a node type owned by another + // plugin. Type "ownership" is first come first served. + if (!typeOwners[node.internal.type] && plugin) { + typeOwners[node.internal.type] = plugin.name + } else { + if (typeOwners[node.internal.type] !== plugin.name) { + throw new Error( + stripIndent` + The plugin "${plugin.name}" created a node of a type owned by another plugin. + + The node type "${node.internal.type}" is owned by "${typeOwners[node.internal.type]}". + + If you copy and pasted code from elsewhere, you'll need to pick a new type name + for your new node(s). + + The node object passed to "createNode": + + ${JSON.stringify(node, null, 4)} + + The plugin creating the node: + + ${JSON.stringify(plugin, null, 4)} + ` + ) + } + } + + const oldNode = getNode(node.id) + + // If the node has been created in the past, check that + // the current plugin is the same as the previous. + if (oldNode && oldNode.internal.owner !== plugin.name) { + throw new Error( + stripIndent` + Nodes can only be updated by their owner. Node "${node.id}" is + owned by "${oldNode.internal.owner}" and another plugin "${plugin.name}" + tried to update it. + + ` + ) + } + // Check if the node has already been processed. - if ( - getNode(node.id) && - !hasNodeChanged(node.id, node.internal.contentDigest) - ) { + if (oldNode && !hasNodeChanged(node.id, node.internal.contentDigest)) { return { type: `TOUCH_NODE`, plugin, @@ -134,6 +184,53 @@ actions.createNode = (node, plugin = ``) => { } } +actions.addFieldToNode = ({ node, fieldName, fieldValue }, plugin) => { + // Ensure required fields are set. + if (!node.internal.fieldOwners) { + node.internal.fieldOwners = {} + } + if (!node.fields) { + node.fields = {} + } + + // Check that this field isn't owned by another plugin. + const fieldOwner = node.internal.fieldOwners[fieldName] + if (fieldOwner && fieldOwner !== plugin.name) { + throw new Error( + stripIndent` + A plugin tried to update a node field that it doesn't own: + + Node id: ${node.id} + Plugin: ${plugin.name} + fieldName: ${fieldName} + fieldValue: ${fieldValue} + ` + ) + } + + // Update node + node.fields[fieldName] = fieldValue + node.internal.fieldOwners[fieldName] = plugin.name + + return { + type: `ADD_FIELD_TO_NODE`, + plugin, + payload: node, + } +} + +actions.addNodeToParent = ({ parent, child }, plugin) => { + // Update parent + parent.children.push(child.id) + parent.children = _.uniq(parent.children) + + return { + type: `ADD_CHILD_NODE_TO_PARENT_NODE`, + plugin, + payload: parent, + } +} + actions.updateSourcePluginStatus = (status, plugin = ``) => { return { type: `UPDATE_SOURCE_PLUGIN_STATUS`, diff --git a/packages/gatsby/src/redux/index.js b/packages/gatsby/src/redux/index.js index da1ab5233b7f6..4ce4ea39991d0 100644 --- a/packages/gatsby/src/redux/index.js +++ b/packages/gatsby/src/redux/index.js @@ -85,7 +85,7 @@ exports.loadNodeContent = node => { // Load plugin's loader function const plugin = store .getState() - .flattenedPlugins.find(plug => plug.name === node.internal.pluginName) + .flattenedPlugins.find(plug => plug.name === node.internal.owner) const { loadNodeContent } = require(plugin.resolve) if (!loadNodeContent) { throw new Error( diff --git a/packages/gatsby/src/redux/reducers/nodes.js b/packages/gatsby/src/redux/reducers/nodes.js index 94d0d83211f96..f3cf4fc2397f0 100644 --- a/packages/gatsby/src/redux/reducers/nodes.js +++ b/packages/gatsby/src/redux/reducers/nodes.js @@ -6,13 +6,8 @@ module.exports = (state = {}, action) => { case `DELETE_CACHE`: return {} case `CREATE_NODE`: - newState = { - ...state, - [action.payload.id]: action.payload, - } - return newState - - case `UPDATE_NODE`: + case `ADD_FIELD_TO_NODE`: + case `ADD_CHILD_NODE_TO_PARENT_NODE`: newState = { ...state, [action.payload.id]: action.payload, diff --git a/packages/gatsby/src/schema/run-sift.js b/packages/gatsby/src/schema/run-sift.js index 4cf20b25ebacf..84781280693d7 100644 --- a/packages/gatsby/src/schema/run-sift.js +++ b/packages/gatsby/src/schema/run-sift.js @@ -62,10 +62,12 @@ module.exports = ({ args, nodes, connection = false, path = `` }) => { if (connection) { const connectionArray = connectionFromArray(result, args) connectionArray.totalCount = result.length - addPageDependency({ - path, - connection: result[0].type, - }) + if (result.length > 0 && result[0].internal) { + addPageDependency({ + path, + connection: result[0].internal.type, + }) + } return connectionArray } diff --git a/packages/gatsby/src/utils/api-runner-node.js b/packages/gatsby/src/utils/api-runner-node.js index ecd4c22a92bb2..e384f47d5dd8a 100644 --- a/packages/gatsby/src/utils/api-runner-node.js +++ b/packages/gatsby/src/utils/api-runner-node.js @@ -19,11 +19,6 @@ const doubleBind = (boundActionCreators, plugin) => { const boundActionCreator = boundActionCreators[key] if (typeof boundActionCreator === `function`) { doubleBoundActionCreators[key] = (...args) => { - // Automatically add to newly created nodes - // the plugin's name - if (key === `createNode`) { - args[0].internal.pluginName = plugin.name - } return boundActionCreator(...args, plugin) } } diff --git a/www/gatsby-node.js b/www/gatsby-node.js index 84d63b2431dda..a17504548b01a 100644 --- a/www/gatsby-node.js +++ b/www/gatsby-node.js @@ -21,8 +21,10 @@ exports.createPages = ({ graphql, boundActionCreators }) => { allMarkdownRemark(limit: 1000) { edges { node { - slug - package + fields { + slug + package + } } } } @@ -35,22 +37,22 @@ exports.createPages = ({ graphql, boundActionCreators }) => { // Create docs pages. result.data.allMarkdownRemark.edges.forEach(edge => { - if (_.includes(edge.node.slug, `/blog/`)) { + if (_.includes(edge.node.fields.slug, `/blog/`)) { upsertPage({ - path: `${edge.node.slug}`, // required + path: `${edge.node.fields.slug}`, // required component: slash(blogPostTemplate), context: { - slug: edge.node.slug, + slug: edge.node.fields.slug, }, }) } else { upsertPage({ - path: `${edge.node.slug}`, // required + path: `${edge.node.fields.slug}`, // required component: slash( - edge.node.package ? packageTemplate : docsTemplate + edge.node.fields.package ? packageTemplate : docsTemplate ), context: { - slug: edge.node.slug, + slug: edge.node.fields.slug, }, }) } @@ -64,7 +66,7 @@ exports.createPages = ({ graphql, boundActionCreators }) => { // Create slugs for files. exports.onNodeCreate = ({ node, boundActionCreators, getNode }) => { - const { updateNode } = boundActionCreators + const { addFieldToNode } = boundActionCreators let slug if (node.internal.type === `File`) { const parsedFilePath = parseFilepath(node.relativePath) @@ -78,8 +80,7 @@ exports.onNodeCreate = ({ node, boundActionCreators, getNode }) => { } } if (slug) { - node.slug = slug - updateNode(node) + addFieldToNode({ node, fieldName: `slug`, fieldValue: slug }) } } else if (node.internal.type === `MarkdownRemark`) { const fileNode = getNode(node.parent) @@ -100,13 +101,15 @@ exports.onNodeCreate = ({ node, boundActionCreators, getNode }) => { parsedFilePath.name === `README` ) { slug = `/docs/packages/${parsedFilePath.dir}/` - node.frontmatter = {} - node.frontmatter.title = parsedFilePath.dir - node.package = true + addFieldToNode({ + node, + fieldName: `title`, + fieldValue: parsedFilePath.dir, + }) + addFieldToNode({ node, fieldName: `package`, fieldValue: true }) } if (slug) { - node.slug = slug - updateNode(node) + addFieldToNode({ node, fieldName: `slug`, fieldValue: slug }) } } } diff --git a/www/src/pages/blog/index.js b/www/src/pages/blog/index.js index cbeb8c8de9169..d2692f55fc1f3 100644 --- a/www/src/pages/blog/index.js +++ b/www/src/pages/blog/index.js @@ -28,7 +28,7 @@ const IndexRoute = React.createClass({ post.frontmatter.author.avatar.childImageSharp.responsiveResolution return (
- +

(