diff --git a/README.md b/README.md index afdf9c5..71dfe7d 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,10 @@ module.exports = { // This is generally the camelcased version of the word // after the 'all' in GraphQL ie. allMyImages type is myImages nodeType: 'myNodes', - // String that is path to the image you want to use, relative to the node. - // This uses lodash .get, see docs for accepted formats [here](https://lodash.com/docs/4.17.11#get). + // For simple object traversal, this is the string path to the image you + // want to use, relative to the node. + // This uses lodash .get, see [docs for accepted formats here](https://lodash.com/docs/4.17.11#get). + // For traversing objects with arrays at given depths, see [how to handle arrays below](#traversing-objects-with-arrays) imagePath: 'path.to.image', // ** ALL OPTIONAL BELOW HERE: ** //Name you want to give new image field on the node. @@ -129,3 +131,53 @@ allMyNodes { } } ``` +### Traversing objects with arrays + +Since some GraphQL APIs will send back objects with nested arrays where your target data lives, `gatsby-plugin-remote-images` also supports traversing objects that have arrays at arbitrary depths. To opt in to this feature, add an array literal, `[]`, to the end of the node you want to indicate is an array. + +##### Note: arrays of image urls at leaf nodes are currently not supported + +Given an object structure like this: +```javascript +allMyNodes { + nodes: [ + { + imageUrl: 'https://...' + }, + ... + ] +} +``` + +To get the images and make them avabilable for the above example, your config should look like this: +```javascript +module.exports = { + plugins: [ + { + resolve: `gatsby-plugin-remote-images`, + options: { + nodeType: 'myNodes', + imagePath: 'nodes[].imageUrl', + }, + }, + ] +} +``` + +Now, if we query `allMyNodes` we can query as we would any gatsby-image node: + +```graphql +allMyNodes { + nodes { + localImage { + childImageSharp { + fluid(maxWidth: 400, maxHeight: 250) { + ...GatsbyImageSharpFluid + } + } + } + } +} +``` + +##### Note: While `lodash .get` doesn't natively support this syntax, it is still used to traverse the object structure, so [the documentation for `.get`](https://lodash.com/docs/4.17.11#get) still applies in full. \ No newline at end of file diff --git a/src/gatsby-node.js b/src/gatsby-node.js index a8b70de..a2fbe0c 100644 --- a/src/gatsby-node.js +++ b/src/gatsby-node.js @@ -13,29 +13,55 @@ exports.onCreateNode = async ( auth = {}, ext = null, } = options + const createImageNodeOptions = { + store, + cache, + createNode, + createNodeId, + auth, + ext, + name, + } - let fileNode + // Check if any part of the path indicates the node is an array and splits at those indicators + let imagePathSegments = [] + if (imagePath.includes("[].")) { + imagePathSegments = imagePath.split("[].") + } if (node.internal.type === nodeType) { - - const url = ext ? `${get(node, imagePath)}${ext}` : get(node, imagePath) - if (!url) { - return + if (imagePathSegments.length) { + await createImageNodesInArrays(imagePathSegments[0], node, { imagePathSegments, ...createImageNodeOptions }) + } else { + const url = getPath(node, path, ext) + await createImageNode(url, node, createImageNodeOptions) } + } +} - try { - fileNode = await createRemoteFileNode({ - url, - parentNodeId: node.id, - store, - cache, - createNode, - createNodeId, - auth, - ext, - }) - } catch (e) { - console.error('gatsby-plugin-remote-images ERROR:', e) - } +// Returns value from path, adding extension when supplied +function getPath(node, path, ext = null) { + const value = get(node, path) + + return ext ? value + ext : value +} + +// Creates a file node and associates the parent node to its new child +async function createImageNode(url, node, options) { + const { name, ...restOfOptions } = options + let fileNode + + if (!url) { + return + } + + try { + fileNode = await createRemoteFileNode({ + ...restOfOptions, + url, + parentNodeId: node.id, + }) + } catch (e) { + console.error('gatsby-plugin-remote-images ERROR:', e) } // Adds a field `localImage` or custom name to the node // ___NODE appendix tells Gatsby that this field will link to another node @@ -43,3 +69,32 @@ exports.onCreateNode = async ( node[`${name}___NODE`] = fileNode.id } } + +// Recursively traverses objects/arrays at each path part, then operates on targeted leaf node +async function createImageNodesInArrays(path, node, options) { + if (!path || !node) { + return + } + const { imagePathSegments, ext } = options + const pathIndex = imagePathSegments.indexOf(path), + isPathToLeafNode = pathIndex === imagePathSegments.length - 1, + nextValue = getPath(node, path, isPathToLeafNode ? ext : null) + + // grab the parent of the leaf node, if it's not the current value of `node` already + let nextNode = node + if (isPathToLeafNode && path.includes('.')) { + const pathToLastParent = path + .split('.') + .slice(0, -1) + .join('.') + nextNode = get(node, pathToLastParent) + } + return Array.isArray(nextValue) + // Recursively call function with next path segment for each array element + ? Promise.all( + nextValue.map(item => createImageNodesInArrays(imagePathSegments[pathIndex + 1], item, options)) + ) + // otherwise, handle leaf node + : createImageNode(nextValue, nextNode, options) +} +