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

feat(gatsby-plugin-image): Add image plugin helpers #28110

Merged
merged 19 commits into from
Nov 26, 2020
Merged

Conversation

ascorbic
Copy link
Contributor

@ascorbic ascorbic commented Nov 16, 2020

The new Gatsby image plugin uses a new data format, which is not compatible with existing resolvers. Plugins that use childImageSharp can use the new resolver gatsbyImageData, with a codemod for easy migration. However this will not work for plugins that provide their own gatsby image-compatible resolvers. These plugins will need to add a new resolver if their users are to be able to make use of the new component, with the improved performance and Lighthouse scores that go along with it.

Plugins that implemented a gatsby image-compatible resolver have previously had to reverse-engineer the sharp transformer, as there was no approved way to easily handle this.

Gatsby image plugin helpers

We are aware that it is asking a lot of plugin authors to add a whole new resolver. With that in mind, we are proposing a new set of helper functions designed to simplify the job, meaning that you only need to provide the logic to generate image URLs, while we handle the boilerplate of creating the resolver and data. The returned data can be passed directly to a GatsbyImage component, and includes all image sources in multiple resolutions and formats needed to create a fully responsive image with lazy-loading, blur-up and low-resolution placeholders.

This is primarily for plugins that provide their own URLs for resized images. Plugins that use sharp for resizing do not need to make any changes, as the new resolvers will be added automatically.

We are also adding a custom hook that allows runtime generation of gatsby image data, meaning these can be used in situations where the image data is loaded at runtime rather than build time. While this loses the benefit of the low-resolution placeholder, it does allow many of the benefits of the component in situations where they were not previously available.

There are three new exports added to gatsby-plugin-image:

generateImageData

This is the core helper function, which is used by the other helpers, and you would not normally use it on its own. It takes a set of options and returns a GatsbyImageData object which can be passed directly to a GatsbyImage component. The most important parameter is the generateImageSource callback function, which allows the source plugin to generate the custom URLs, when given an image, dimensitons and format. A simple example would be:

const generateImageSource = (imageName, width, height, format, fit) => {
  const src = `https://myexamplehost.com/${width}x${height}/${imageName}.${format}`
  return { src, width, height, format }
}

It returns a width, height and format as well as the URL, because it can override the requested format. This might happen if the requested size is too large, for example, or if the format is unsupported.

The rest of the options are mostly passed-through directly. A minimal example could be:

  const data = generateImageData({
    width: 600,
    height: 800,
    layout: 'fixed',
    formats: ['auto', 'webp', 'avif']
    filename: 'example.png',
    generateImageSource,
  })
}

A more comprehensive example can be seen in the getGatsbyImageFieldConfig resolver below.

This helper is available at both compile time and runtime, but you must be careful to not perform any expensive operations at runtime. In those cases you should use it at compile time, by adding a custom resolver as shown below.

getGatsbyImageFieldConfig

This helper function is designed to make it easier to add a gatsbyImageData resolver to a source plugin. It is imported from "gatsby-plugin-image/graphql-utils". As it is run at compile time, you can include async, expensive operations such as downloading base64-encoded preview images. The returned field config includes all the arguments needed by the helper, including width, height, maxWidth, maxHeight, layout, formats etc. The fields are pre-defined with descriptions, and you can also pass additional definitions for custom arguments. Usage is like this:

import { getGatsbyImageFieldConfig } from "gatsby-plugin-image/graphql-utils"

const generateImageSource = (imageName, width, height, format, fit) => {
  const src = `https://myexamplehost.com/${width}x${height}/${imageName}.${format}`
  return { src, width, height, format }
}
const resolveGatsbyImageData = async (image, options) => {
  const { filename, ...sourceMetadata } = getBasicImageProps(image, options)

  return generateImageData({
    ...options,
    pluginName: `gatsby-source-myexample`,
    sourceMetadata,
    filename,
    placeholderURL: await getBase64Image({ baseUrl })
    generateImageSource,
    options,
  })
}

const gatsbyImageData = getGatsbyImageFieldConfig(resolveGatsbyImageData, {
    jpegProgressive: {
      type: GraphQLBoolean,
      defaultValue: true,
    },
    cropFocus: {
      type: ImageCropFocusType,
    },
  })

For a full example, see this PR for Contentful, which adds a resolver for gatsbyImageData.

useGatsbyImage

This is a custom hook which can be used at runtime, designed to be used to create more specialised hooks targeting specific image hosts. It should be used when you can deterministically create a URL based on the requested parameters, such as with an image host like Cloudinary. It should be used in cases where no pre-processing needs to happen at compile time. Bear in mind that this means you cannot create placeholder images such low-res blurred or traced SVG images. The big deal here is that it can work with fully dynamic image URLs. Usage is as follows:

const generateImageSource = (imageName, width, height, format, fit) => {
  const src = `https://myexamplehost.com/${width}x${height}/${imageName}.${format}`
  return { src, width, height, format }
}

export const useMyImageHost = (props) =>
  useGatsbyImage({
    pluginName: `useMyImageHost`,
    generateImageSource,
		...props
  })

export function MyImage({ image, maxWidth, maxHeight, ...props }) {
  const data = useMyImageHost({
    filename: image,
    maxWidth,
    maxHeight,
    layout: `constrained`,
  })
  return <GatsbyImage image={data} {...props} />
}

For a full example, see this custom hook for Cloudinary

Which should I use?

Ideally you should use the custom resolver, as it is the only way to include a placeholder image that displays without network access. This gives the best performance and user experience. However that is not always possible. The custom hook can be used in situation where the image URL is dynamically loaded at runtime, such as for search results or live data from an external source. In these situations, setting a background color is a good way to provide a good loading experience. If you control the API, consider providing a "dominant color" property alongside your image URLs, which allows you to provide an even better experience.

  • If you need to do any expensive operations such as generating low-resolution previews, create a custom resolver using getGatsbyImageFieldConfig and generate them at build time.
  • If you need an easy migration for users from an existing custom resolver, use getGatsbyImageFieldConfig and make a PR to update the gatsby image codemod to support your plugin.
  • If you're ok with not generating a placeholder image, and can cheaply and deterministically generate a URL when given a base image, dimensions and format, you can create a custom hook based on useGatsbyImage
  • If you don't need to create a full plugin, and just want to make is easy for users to use your image host, use the custom hook

[ch19256]

@gatsbot gatsbot bot added the status: triage needed Issue or pull request that need to be triaged and assigned to a reviewer label Nov 16, 2020
@ascorbic ascorbic force-pushed the feat/image-helpers branch 2 times, most recently from 92cb349 to ecb1fe7 Compare November 16, 2020 19:07
@pvdz pvdz added topic: media Related to gatsby-plugin-image, or general image/media processing topics and removed status: triage needed Issue or pull request that need to be triaged and assigned to a reviewer labels Nov 17, 2020
@ascorbic ascorbic changed the title WIP: Add image helper WIP: feat(gatsby-plugin-image): Add image plugin helpers Nov 23, 2020
@ascorbic ascorbic marked this pull request as ready for review November 24, 2020 15:22
@ascorbic ascorbic changed the title WIP: feat(gatsby-plugin-image): Add image plugin helpers feat(gatsby-plugin-image): Add image plugin helpers Nov 24, 2020
@@ -0,0 +1 @@
export * from "./dist/resolver-utils"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved the graphql helper into a separate subpackage. Having them in the main gatsby-plugin-image was causing build problems, as it was trying to import graphql it when building SSR pages, causing build failures. Moving it into "gatsby-plugin-image/graphql" fixes that. I'm guessing this is the reason gatsby re-exports graphql from a subpackage too?

mfrachet
mfrachet previously approved these changes Nov 26, 2020
Copy link
Contributor

@mfrachet mfrachet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks reasonable to me 👍🏻

@ascorbic ascorbic added the bot: merge on green Gatsbot will merge these PRs automatically when all tests passes label Nov 26, 2020
@gatsbybot gatsbybot merged commit 6ed397f into master Nov 26, 2020
@delete-merged-branch delete-merged-branch bot deleted the feat/image-helpers branch November 26, 2020 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bot: merge on green Gatsbot will merge these PRs automatically when all tests passes topic: media Related to gatsby-plugin-image, or general image/media processing topics
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants