Skip to content

Commit 3f8399b

Browse files
ascorbicgillkylefrederickfogertygatsbybotLB
authored
docs(gatsby-plugin-image): Add image plugin toolkit docs (#29483)
* wip: plugin docs * Apply suggestions from code review Co-authored-by: Kyle Gill <[email protected]> * Add more info * Add info on image helper * typo Co-authored-by: Frederick Fogerty <[email protected]> * Updates * Apply suggestions from code review Co-authored-by: LB <[email protected]> * Changes from review Co-authored-by: Kyle Gill <[email protected]> Co-authored-by: Frederick Fogerty <[email protected]> Co-authored-by: gatsbybot <[email protected]> Co-authored-by: LB <[email protected]>
1 parent 4b7fe37 commit 3f8399b

File tree

1 file changed

+263
-0
lines changed

1 file changed

+263
-0
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
---
2+
title: Adding Gatsby Image support to your plugin
3+
---
4+
5+
The [new Gatsby image plugin](https://www.gatsbyjs.com/plugins/gatsby-plugin-image) includes React components for displaying images, and these can be used with data from plugins. The plugin handles all of the hard parts of displaying responsive images that follow best practices for performance. In fact we are confident that it is the fastest way to render images in React, as it can handle blur-up and lazy-loading before React hydration.
6+
Support for these are available out of the box in `gatsby-transformer-sharp`, so if your plugin downloads images and processes them locally then your users can use the [`gatsbyImageData` resolver](https://www.gatsbyjs.com/docs/how-to/images-and-media/using-gatsby-plugin-image#dynamic-images). However, if your CDN can deliver images of multiple sizes with a URL-based API, then the plugin includes a toolkit to allow you to give your users the same great experience without needing to download the images locally. It also allows you to create components that display these images dynamically at runtime, without needing to add a GraphQL resolver.
7+
8+
## Adding a `gatsbyImageData` GraphQL resolver
9+
10+
You can give your users the best experience by adding a `gatsbyImageData` resolver to your image nodes. This allows you to generate low-resolution or traced SVG images as inline data URIs to use as placeholders. You can also calculate the image's dominant color for an alternative placeholder. These are the same placeholders that are included with `gatsby-transformer-sharp`, and will give the best experience for your users. If you are able to deliver these directly from your CMS or other data source then this is ideal, but otherwise you can use helper functions included in `gatsby-plugin-sharp`.
11+
12+
There are three steps to add a basic `gatsbyImageData` resolver:
13+
14+
1. [Create the `generateImageSource` function](#create-the-generateimagesource-function)
15+
2. [Create the resolver function](#create-the-resolver-function)
16+
3. [Add the resolver](#add-the-resolver)
17+
18+
### Create the `generateImageSource` function
19+
20+
The `generateImageSource` function is where you generate your image URLs. The image plugin calculates which sizes and formats are needed, according to the format, size and breakpoints requested by the user. For each of these, your function is passed the base URL, width, height and format (i.e. the image filetytpe), as well as any custom options that your plugin needs. You then return the generated URL. The returned object also includes width, height and format. This means you can return a different value from the one requested. For example, if the function requests an unsupported format or size, you can return a different one which will be used instead.
21+
22+
```js:title=gatsby-source-example/gatsby-node.js
23+
// In this example we use a custom `quality` option
24+
const generateImageSource = (baseURL, width, height, format, fit, options) => {
25+
const src = `https://myexampleimagehost.com/${baseURL}?w=${width}&h=${height}&fmt=${format}&q=${options.quality}`
26+
return { src, width, height, format }
27+
}
28+
```
29+
30+
### Create the resolver function
31+
32+
You can then use the function created in the previous step to build your resolver function. It can be an async function, and it should return the value from `generateImageData`. An example resolver could look like this:
33+
34+
```js:title=gatsby-source-example/gatsby-node.js
35+
36+
import { generateImageData, getLowResolutionImageURL } from "gatsby-plugin-image"
37+
38+
39+
const resolveGatsbyImageData = async (image, options) => {
40+
// The `image` argument is the node to which you are attaching the resolver,
41+
// so the values will depend on your data type.
42+
const filename = image.src
43+
44+
const sourceMetadata = {
45+
width: image.width,
46+
height: image.height,
47+
// In this example, the node has a value like "image/png", which needs
48+
// converting to a value such as "png". If this omitted, the funciton will
49+
// attempt to work it out from the file extension.
50+
format: image.mimeType.split("/")[1]
51+
}
52+
53+
const imageDataArgs = {
54+
...options,
55+
// Passing the plugin name allows for better error messages
56+
pluginName: `gatsby-source-example`,
57+
sourceMetadata,
58+
filename,
59+
placeholderURL
60+
generateImageSource,
61+
options,
62+
}
63+
64+
// Generating placeholders is optional, but recommended
65+
if(options.placeholder === "blurred") {
66+
// This function returns the URL for a 20px-wide image, to use as a blurred placeholder
67+
// You need to download the image and convert it to a base64-encoded data URI
68+
const lowResImage = getLowResolutionImageURL(imageDataArgs)
69+
70+
// This would be your own function to download and generate a low-resolution placeholder
71+
imageDataArgs.placeholderURL = await getBase64Image(lowResImage)
72+
}
73+
74+
// You could also calculate dominant color, and pass that as `backgroundColor`
75+
// gatsby-plugin-sharp includes helpers that you can use to generate a tracedSVG or calculate
76+
// the dominant color of a local file, if you don't want to handle it in your plugin
77+
78+
79+
return generateImageData(imageDataArgs)
80+
}
81+
82+
```
83+
84+
### Add the resolver
85+
86+
You should register the resolver using the [`createResolvers` API hook](https://www.gatsbyjs.com/docs/reference/config-files/gatsby-node/#createResolvers). `gatsby-plugin-image/graphql-utils` includes an optional utility function to help with this. It registers the resolver with all of the base arguments needed to create the image data, such as width, aspect ratio, layout and background color. These are defined with comprehensive descriptions that are visible when your users are building queries in GraphiQL.
87+
88+
You can pass additional arguments supported by your plugin, for example, image options such as quality. However, if you want complete control over the resolver args, then you will want to create it yourself from scratch. We recommend keeping the args similar to the default, as this is what users will be expecting, and it means you benefit from the plugin documentation. At a minimum, you should always expose `layout`, `width` and `height` as args.
89+
90+
The arguments:
91+
92+
- `resolverFunction`: the resolver function that you created in step 1. It receives the node and the arguments and should return the image data object.
93+
- `additionalArgs`: an object defining additional args, in the same format used by [Gatsby Type Builders](https://www.gatsbyjs.com/docs/reference/graphql-data-layer/schema-customization/#gatsby-type-builders)
94+
95+
For example, to add a `gatsbyImageData` resolver onto a `ProductImage` node that you have previously defined:
96+
97+
```js:title=gatsby-source-example/gatsby-node.js
98+
// Note the different import
99+
import { getGatsbyImageResolver } from "gatsby-plugin-image/graphql-utils"
100+
101+
export function createResolvers({ createResolvers }) {
102+
createResolvers({
103+
ProductImage: {
104+
// loadImageData is your custom resolver, defined in step 2
105+
gatsbyImageData: getGatsbyImageResolver(loadImageData, {
106+
quality: "Int",
107+
}),
108+
},
109+
})
110+
}
111+
```
112+
113+
## Adding a custom image component
114+
115+
If you have a URL-based image API, you can create a custom image component that wraps `<GatsbyImage />` and displays images generated at runtime. If you have a source plugin, this can accept your native image object or it can take a base URL and generate the image based on that. This is a good solution for image CDNs that aren't handling their own CMS data, and can generate a transformed image from a source URL and dimensions.
116+
117+
There are three steps to create a custom image component:
118+
119+
1. [Create your URL builder function](#create-your-url-builder-function)
120+
2. [Create your image data function](#create-your-image-data-function)
121+
3. [Create your wrapper component](#create-your-wrapper-component) (optional)
122+
123+
### Create your URL builder function
124+
125+
This is similar to the `generateImageSource` approach described above. The difference is that it returns a URL string. This is an example for the same image host:
126+
127+
```js
128+
function urlBuilder({ baseUrl, width, height, format, options }) {
129+
return `https://myexampleimagehost.com/${baseURL}?w=${width}&h=${height}&fmt=${format}&q=${options.quality}`
130+
}
131+
```
132+
133+
If your host supports it, we recommend using auto-format to deliver next-generation image formats such as WebP or AVIF to supported browsers. In this case, ignore the `format` option.
134+
135+
### Create your image data function
136+
137+
This is similar to the image resolver described above. However, because it executes in the browser you can't use node APIs. Because it needs to run before the image loads, it should be fast and synchronous. You will not be downloading and generating base64 placeholders, for example, or calculating dominant colors. If you have these values pre-calculated then you can pass these in and use them, but they need to be available in the props that you pass to the function at runtime. Any placeholder that is generated asynchronously would defeat the purpose: it prevents SSR, and is almost certainly slower than just downloading the main image.
138+
139+
The function should accept the props that will be passed into your component, and at a minimum it needs to take the props required by the `getImageData` helper function from `gatsby-plugin-image`. Here is an example for an image host:
140+
141+
```js
142+
import { getImageData } from "gatsby-plugin-image"
143+
144+
export function getExampleImageData({ image, ...props }) {
145+
return getImageData({
146+
baseUrl: image.url,
147+
sourceWidth: image.width,
148+
sourceHeight: image.height,
149+
urlBuilder,
150+
pluginName: "gatsby-source-example",
151+
// If your host supports auto-format/content negotiation, pass this as the formats array
152+
formats: ["auto"],
153+
...props,
154+
})
155+
}
156+
```
157+
158+
You can export this function as a public API, and users can use the function to generate data to pass to `GatsbyImage`:
159+
160+
```jsx
161+
// This might come from an API at runtime
162+
const image = {
163+
url: "kitten.jpg",
164+
width: 800,
165+
height: 600,
166+
}
167+
const imageData = getExampleImageData({ image, layout: "fixed", width: 400 })
168+
return <GatsbyImage image={imageData} alt="Kitten" />
169+
```
170+
171+
### Create your wrapper component
172+
173+
This stage is optional: you may prefer to share the image data function and let your users pass the result to `<GatsbyImage>`, as shown above. However, the developer experience is better with a custom image component.
174+
175+
The component should accept the same props as your image data function, as well as all of the props for `<GatsbyImage>` which it can pass down to that component. Here's how you might type the props in TypeScript:
176+
177+
```typescript
178+
interface ImageComponentProps
179+
//This is the type for your image data function
180+
extends GetGatsbyImageDataProps,
181+
// We omit "image" because that's the prop that we generate,
182+
Omit<GatsbyImageProps, "image"> {
183+
// Any other props can go here
184+
myCustomProp?: string
185+
}
186+
```
187+
188+
Your component can accept just a URL if that's enough to identify the image, or you can pass a full object. This can be an object that you have passed in through your GraphQL API, or it can be data coming from outside of Gatsby, such as via search results or a shopping cart API. Unlike the GraphQL resolvers or the built-in `StaticImage` component, this can be dynamic data that changes at runtime.
189+
190+
For best results, you should pass in the dimensions of the source image, so make sure to include that data if it's available. With dimensions, the plugin can calculate aspect ratio and the maximum size image to request.
191+
192+
The component itself should wrap `<GatsbyImage>`, using your image data function to generate the object to pass to it.
193+
194+
```jsx
195+
import * as React from "react"
196+
import { GatsbyImage } from "gatsby-plugin-image"
197+
import { getExampleImageData } from "./my-image-data"
198+
199+
export function ExampleImage({
200+
// Destructure the props that you are passing to the image data function
201+
image,
202+
width,
203+
height,
204+
layout,
205+
backgroundColor,
206+
sizes,
207+
aspectRatio,
208+
options,
209+
// Use ...rest for the GatsbyImage props
210+
...props
211+
}) {
212+
const imageData = getExampleImageData({
213+
image,
214+
width,
215+
height,
216+
layout,
217+
backgroundColor,
218+
sizes,
219+
aspectRatio,
220+
options,
221+
})
222+
223+
// Pass the image data and spread the rest of the props
224+
return <GatsbyImage image={imageData} {...props} />
225+
}
226+
```
227+
228+
The user could then use the component like this:
229+
230+
```jsx
231+
// This might come from an API at runtime
232+
const image = {
233+
url: "baseurl.jpg",
234+
width: 800,
235+
height: 600,
236+
}
237+
238+
return (
239+
<ExampleImage
240+
image={image}
241+
layout="fixed"
242+
width={400}
243+
backgroundColor="#660033"
244+
alt="My image"
245+
/>
246+
)
247+
```
248+
249+
A different component for an image CDN might not expect the user to know the dimensions of the source image, and might want to allow them to just pass a base URL:
250+
251+
```jsx
252+
<ExampleImage
253+
image="https://example.com/nnnnn/bighero.jpg"
254+
loading="eager"
255+
layout="fullWidth"
256+
aspectRatio={16 / 9}
257+
alt=""
258+
/>
259+
```
260+
261+
## Other considerations
262+
263+
You should add `gatsby-plugin-image` as a peer dependency to your plugin, and tell your users to install it in your docs. Don't add it as a direct dependency, as this could lead to multiple versions being installed. You can refer users to [the `gatsby-plugin-image` docs](https://www.gatsbyjs.com/docs/how-to/images-and-media/using-gatsby-plugin-image/) for instructions on using the component. However, be sure to document the specifics of your own resolver: it may not be immediately clear to users that the args are different from those in sharp. You may want to highlight specific differences, such as if you don't support AVIF images, or if you do support GIFs, as well as explaining the placeholders that you support.

0 commit comments

Comments
 (0)