Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Responsive Images (remote) #2902

Closed
kildareflare opened this issue Nov 13, 2017 · 15 comments · May be fixed by tejzpr/gatsby#61
Closed

Responsive Images (remote) #2902

kildareflare opened this issue Nov 13, 2017 · 15 comments · May be fixed by tejzpr/gatsby#61

Comments

@kildareflare
Copy link

Hi, first up great work. I've just completed my first site using Gatsby and it's awesome!
My first time using React and GraphQL and I've enjoyed it (normally use Angular1/Angular2).

My site is backed by the prismic headless CMS and uses this source plugin to get the data.

I'd now like to make the images responsive.

I'm aware that Gatsby has a responsive image plugin but it appears to work only with local images and mine are hosted remotely in the prismic CMS. In theory it would be possible to add multiple images in prismic and infer what to display where based on the data returned. But I'd rather this was all handled automagically and only one image needed to be added to prismic.

Question. Is there a way to handle remote images with the existing plugin - have I missed something?

I'm assuming not, in which case would the best thing be to:

  • extend the plugin with a new option, that when set, fetches remote images, saves them locally and then processed them as normal?
  • create a new plugin to do this?
@garnerp
Copy link
Contributor

garnerp commented Nov 13, 2017

I don't know much about the Prismic plugin, but take a look at the Contentful one. I believe that it uses Gatsby's responsive image solution to generate the srcset, but the resulting URLs are served from Contentful's image delivery using its image manipulation query string parameters.

https://using-contentful.netlify.com/

@KyleAMathews
Copy link
Contributor

Contentful is a bit unique in that they have an Image API. So what the gatsby-source-contentful plugin does is create a GraphQL type for images with the same API as gatsby-transformer-sharp so you can write queries the same way but not have to do the image processing locally like with sharp.

Assuming Prismic doesn't have an image API like contentful, you'll want to do your first option, add a PR to gatsby-source-prismic to download images locally.

This is pretty easy to do — gatsby-source-filesystem exposes a helper function which does all the hard work. See for example how gatsby-source-wordpress does things

exports.downloadMediaFiles = async ({ entities, store, cache, createNode }) =>

@kildareflare
Copy link
Author

Thanks for the quick response and the links.

Prismic has no image API so i'll look at implementing something like you suggest.

Before I look at adding this to the source plugin, I should be able to achieve the same in my local gatsby-nodes.js, right?

But that said, how would I debug this? I.e. normally I simply run gatsby develop but how can I insert breakpoints into gatsby-nodes.js so that I can inspect the code?

@KyleAMathews
Copy link
Contributor

Yup! You can use the same createRemoteFileNode function in gatsby-node.js and onCreateNode to download URLs and create links to them with createNodeField.

This is pretty easy to debug with console.log/breakpoints in your API implementations.

@elliotschultz
Copy link

This is great! I didn't realise there was already support for pulling remote images in gatsby-source-wordpress.

I've just tried @KyleAMathews suggestion above (with gatsby-source-contentful instead of Prismic) and I'm getting close but would really appreciate some pointers.

This is my gatsby-node.js file:

const { createRemoteFileNode } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, boundActionCreators, store, cache }) => {
  const { createNodeField, createNode } = boundActionCreators

  if (node.internal.type === `ContentfulAsset`) {
    createRemoteFileNode({
      url: 'http:' + node.file.url,
      store,
      cache,
      createNode
    }).then((result) => {
      console.log(result)
    })

  }
}

This is working as I can see the newly generated File nodes in GraphQL and the remote images are being pulled down to my machine, but I'm not quite understanding how to "create links to them with createNodeField". How do I make these File nodes as a subset of the ContentfulAssetnodes?

@KyleAMathews
Copy link
Contributor

KyleAMathews commented Nov 15, 2017

You'll want to add a link to the new file node using createNodeField e.g.

createNodeField({
  node,
  name: `linkToFile___NODE`,
  value: remoteFile.id,
})

// The field value is now accessible at node.fields.linkToFile

The important part is the name of the new field has the ___NODE at the end which is how you programmatically create links between nodes.

@elliotschultz
Copy link

Thanks for the quick reply

This is my gatsby-node.js now:

exports.onCreateNode = ({ node, boundActionCreators, store, cache }) => {
  const { createNodeField, createNode } = boundActionCreators

  if (node.internal.type === `ContentfulAsset`) {
    return new Promise((resolve, reject) => {
      createRemoteFileNode({
        url: 'http:' + node.file.url,
        store,
        cache,
        createNode
      }).then(result => {

        console.log(result.id)

        createNodeField({
          node,
          name: `linkToFile___NODE`,
          value: result.id
        })
        resolve()
      }).catch(reject)
    })
  }
}

but am getting this error:

Error: Invariant Violation: Encountered an error trying to infer a GraphQL type for: "internal.fieldOwners.linkToFile___NODE". There is no corresponding node with the id field matching: "default-site-plugin"

The console.log(result.id) returns:

/[MY DIRECTORY]/gatsby-contentful/.cache/gatsby-source-filesystem/a2c788e43a91530c5a610bb3072eb449.jpg absPath of file

which appears to be right.

Am I using createNodeField appropriately?

@KyleAMathews
Copy link
Contributor

Check what the value of node is when you create the node field.

@elliotschultz
Copy link

Running console.log(node) right before createNodeField in the code above spits out:

{ id: 'c3yW5oX7VLWOQw0kUoeawKY',
  parent: null,
  children: [],
  file: 
   { url: '//images.contentful.com/hmylqvbzvbxb/3yW5oX7VLWOQw0kUoeawKY/e722bd7caeb2ced91760497f9ee08466/20170715-DSCF2224-Edit-cropped.jpg',
     details: { size: 1944471, image: [Object] },
     fileName: '20170715-DSCF2224-Edit-cropped.jpg',
     contentType: 'image/jpeg' },
  title: 'Portrait',
  description: '',
  node_locale: 'en-AU',
  internal: 
   { type: 'ContentfulAsset',
     contentDigest: '007eea3e43ba39629d71d11f0379548d',
     owner: 'gatsby-source-contentful' }
}
@KyleAMathews
Copy link
Contributor

The error suggests you're somehow adding the field to a random other node.

@toxsick
Copy link
Contributor

toxsick commented Dec 22, 2017

@elliotschultz did you fix this one? I'm also getting

Error: Invariant Violation: Encountered an error trying to infer a GraphQL type for: "internal.fieldOwners.<fieldname>___NODE". There is no corresponding node with the id field matching: "default-site-plugin"

when trying to create a link between nodes.

@moltar
Copy link

moltar commented Jan 20, 2018

Also getting the same error:

Invariant Violation: Encountered an error trying to infer a GraphQL type for: "internal.fieldOwners.linkToFile___NODE". The re is no corresponding node with the id field matching: "default-site-plugin"

@m4rrc0
Copy link
Contributor

m4rrc0 commented Mar 8, 2018

Did someone succeeded implementing createRemoteFileNode with createNodeField??
If someone is interested I managed to get around it by creating a node like transformer plugins do. I used this example to help me think about it the right way.

const crypto = require('crypto')
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)

const createContentDigest = obj =>
  crypto
    .createHash(`md5`)
    .update(JSON.stringify(obj))
    .digest(`hex`)

exports.onCreateNode = async ({ node, boundActionCreators, store, cache }) => {
  const {
    createNode,
    createNodeField,
    createParentChildLink,
  } = boundActionCreators

  if (node.internal.type !== `Asset`) {
    return
  }

  const localImageNode = await createLocalImageNode({
    url: 'https://static.pexels.com/photos/248797/pexels-photo-248797.jpeg',
    parent: node.id,
    store,
    cache,
    createNode,
  })
  createParentChildLink({
    parent: node,
    child: localImageNode,
  })
}

const createLocalImageNode = async ({
  url,
  parent,
  store,
  cache,
  createNode,
}) => {

  const fileNode = await createRemoteFileNode({
    url,
    store,
    cache,
    createNode,
  })

  const localImageNode = {
    id: `${parent} >>> LocalImage`,
    url,
    // expires: screenshotResponse.data.expires,
    parent,
    children: [],
    internal: {
      type: `LocalImage`,
    },
    localImageFile___NODE: fileNode.id,
  }

  localImageNode.internal.contentDigest = createContentDigest(localImageNode)
  
  createNode(localImageNode)
  
  return localImageNode
}

The pain is it make kilometric queries

query{
  asset {
    childLocalImage {
      localImageFile {
        childImageSharp {
          sizes {
            src
          }
        }
      }
    }
  }
}
@graysonhicks
Copy link
Contributor

@MarcCoet Awesome, worked for me.

@KyleAMathews
Copy link
Contributor

Due to the high volume of issues, we're closing out older ones without recent activity. Please open a new issue if you need help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
8 participants