-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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: Add new image resolvers #27443
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added some api questions. I don't think we need all options that we currently have in fixed & fluid. We can always add them later.
This only applies when layout = FLUID or CONSTRAINED. For other layout types, use "width"`, | ||
}, | ||
maxHeight: { | ||
type: GraphQLInt, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we add description here too?
`, | ||
}, | ||
height: { | ||
type: GraphQLInt, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
webP: { | ||
type: GraphQLBoolean, | ||
defaultValue: true, | ||
description: `Generate images in WebP format as well as matching the input format. This is the default (and strongly recommended), but will add to processing time.`, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason why we want this option? :) I don't immediately see the need to disable it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doubles the processing time, so some sites where that's a big problem might want to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Or maybe not doubles, but increases signficantly)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The biggest reason why I wouldn't want people to disable it in here is that they value DX over UX. With lazy images and query-on-demand, this becomes less of an issue IMO but I do get your point.
What if we change the description to talk more about performance and not the built time.
description: `Generate images in WebP format as well as matching the input format. This is the default (and strongly recommended), it improves your website performance.`,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the default was false
during gatsby develop
(i.e. NODE_ENV === development) for better DX perf, but true
during gatsby build
(i.e. NODE_ENV === production) for better UX?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we'll fix this with lazy images #27603
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Build time is still important in production. My last job had a site that wouldn't build within the Netlify time limit if we had WebP enabled. We moved it to AWS, but it was still something we needed to be able to disable.
My plan for the new API will be to allow the user to pass an array of formats, with the default being [AUTO, WEBP]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Build times are never more important than users. That's why I want this description to be different.
base64Width: { | ||
type: GraphQLInt, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do the same as tracedSVGOptions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? Combine all base64 options into a single object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes so it's all in one option, I know we only have base64Width but than it's on par with tracedSVG
jpegProgressive: { | ||
type: GraphQLBoolean, | ||
defaultValue: true, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsure if we need this option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I just copied it from the current options
quality: { | ||
type: GraphQLInt, | ||
}, | ||
jpegQuality: { | ||
type: GraphQLInt, | ||
}, | ||
pngQuality: { | ||
type: GraphQLInt, | ||
}, | ||
webpQuality: { | ||
type: GraphQLInt, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we move these things into another inputType so it doesn't add up to args?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean create an options object for each format?
toFormatBase64: { | ||
type: ImageFormatType, | ||
defaultValue: ``, | ||
description: `Force output format. Default is to use the same as the input format`, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsure if this makes sense into gatsbyProps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, maybe in an options object for the blurred preview
cropFocus: { | ||
type: ImageCropFocusType, | ||
defaultValue: sharp.strategy.attention, | ||
}, | ||
fit: { | ||
type: ImageFitType, | ||
defaultValue: sharp.fit.cover, | ||
}, | ||
background: { | ||
type: GraphQLString, | ||
defaultValue: `rgba(0,0,0,1)`, | ||
}, | ||
rotate: { | ||
type: GraphQLInt, | ||
defaultValue: 0, | ||
}, | ||
trim: { | ||
type: GraphQLFloat, | ||
defaultValue: false, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to put this into an inputType as well?
const ProgressBar = require(`progress`) | ||
|
||
import ProgressBar from "progress" | ||
import sharp from "sharp" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use
import sharp from "sharp" | |
import sharp from "./safe-sharp" |
@wardpeet @laurieontech @gillkyle I'm going to create a doc with all the options and we can discuss them in more depth. I think it does make sense to put some time into getting this right. |
Here is the RFC with the new options proposal: #27668 |
* tests: Add visual regression tests * Add config * Wait for images to load * Do multiple window sizes * Adjust settings for comparisons * Fix wrapper * Add 1024 window * Save reports * Update fixed image too large snapshot * Disable video * Update readme * Update element id * Force 1x pixel density * Update readme
…o feat/image-resolvers
…o feat/image-resolvers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have anything really blocking. Some small nits and follow-ups but nothing to block this PR from being merged as long as we fix those follow ups.
Visual regression test is failing for me locally;
The visual regression test is a great addition! Thank you!
What's next?
- We'll need e2e test for disabled javascript and when nativeImageLazyLoading is not available.
- test if eager is actually working as expected
- move gatsby-static-image to gatsby-image e2e test
- Test gatsbyImage resolver in the e2e tests
"data-placeholder-image": ``, | ||
style: { | ||
opacity: isLoaded ? 0 : 1, | ||
transition: `opacity 500ms linear`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be 250 like the main image?
transition: `opacity 500ms linear`, | |
transition: `opacity 250ms linear`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gillkyle Could you look at this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the placeholder needs to fade out, I made the placholder 500ms so you won't have a really gradual fade in from the empty space as the main image fades in (in 250ms). You can see what this looks like in the current state vs the suggested:
250ms for placeholder, 500ms for main | 500ms for placeholder, 500ms for main image |
---|---|
} | ||
|
||
return result | ||
} | ||
|
||
export type PlaceholderImageAttrs = ImgHTMLAttributes<HTMLImageElement> & | ||
Pick<PlaceholderProps, "sources" | "fallback"> | ||
Pick<PlaceholderProps, "sources" | "fallback"> & { | ||
"data-placeholder-image"?: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow up:
Let's move to data-gatsby-image-placeholder
, let's do the same with main image
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea
export const Placeholder: FunctionComponent<PlaceholderProps> = function Placeholder({ | ||
fallback, | ||
...props | ||
}) { | ||
return ( | ||
<Picture | ||
{...props} | ||
fallback={{ | ||
src: fallback, | ||
}} | ||
aria-hidden | ||
alt="" | ||
/> | ||
) | ||
if (fallback) { | ||
return ( | ||
<Picture | ||
{...props} | ||
fallback={{ | ||
src: fallback, | ||
}} | ||
aria-hidden | ||
alt="" | ||
/> | ||
) | ||
} else { | ||
return <div {...props}></div> | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow up:
this is going to cause issue with composability when we want devs to create their own version. Can we add a background prop? This allows us to update proptypes to be fallback or background
if (!image) { | ||
if (process.env.NODE_ENV === `development`) { | ||
console.warn(`[gatsby-plugin-image] Missing image prop`) | ||
} | ||
return null | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow up:
Shouldn't this be handled by propTypes?
target.style.opacity = 1; | ||
if (placeholder) { | ||
placeholder.style.opacity = 0; | ||
placeholder.style.transition = "opacity 500ms linear"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be 250?
placeholder.style.transition = "opacity 500ms linear"; | |
placeholder.style.transition = "opacity 250ms linear"; |
|
||
const primaryImage = images[primaryIndex] | ||
|
||
if (!images?.length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
images should always be a [] unsure why ou need the ?
if (!images?.length) { | |
if (!images.length) { |
const transforms = imageSizes.sizes.map(outputWidth => { | ||
const width = Math.round(outputWidth) | ||
const transform = createTransformObject({ | ||
...args, | ||
width, | ||
height: Math.round(width / imageSizes.aspectRatio), | ||
toFormat: `webp`, | ||
}) | ||
return transform | ||
}) | ||
|
||
const webpImages = batchQueueImageResizing({ | ||
file, | ||
transforms, | ||
reporter, | ||
}) | ||
const webpSrcSet = getSrcSet(webpImages) | ||
|
||
imageProps.images.sources?.push({ | ||
srcSet: webpSrcSet, | ||
type: `image/webp`, | ||
sizes, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow up:
Any reason why we don't append webp to the previous batch? Wouldn't that speed up things? (less active jobs)
Or isn't it possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it should be possible. I'm goign to handle it differently with the updated API
webP: { | ||
type: GraphQLBoolean, | ||
defaultValue: true, | ||
description: `Generate images in WebP format as well as matching the input format. This is the default (and strongly recommended), but will add to processing time.`, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we'll fix this with lazy images #27603
outputPixelDensities: { | ||
type: GraphQLList(GraphQLInt), | ||
description: stripIndent` | ||
A list of image pixel densities to generate, for high-resolution (retina) screens. It will never generate images larger than the source, and will always a 1x image. | ||
Default is [ 1, 2 ] for fixed images, meaning 1x, 2x, 3x, and [0.25, 0.5, 1, 2] for fluid. In this case, an image with a fluid layout and width = 400 would generate images at 100, 200, 400 and 800px wide`, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For fluid this isn't working, this needs to be GraphQLFloat
This PR adds a new GraphQL API for images, for use with the new gatsby-plugin-image. It adds a new resolver to the
ImageSharp
node, with a single fieldimageData
. Unlike the existingfixed
andfluid
resolvers, this returns a JSON type, meaning you don't specify the individual fields, but are instead given the whole object. This is because the object is then passed in to the<GatsbyImage>
component. The API is like this:The helper function
getImage
takes a file node and returnsfile?.childImageSharp?.gatsbyImage?.imageData
Because this no longer uses fragments to specify which fields to return, it instead uses arguments passed to the resolver. These include:
placeholder
: Format of generated placeholder image.DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. (default) Currently disabled until sharp is updated
BLURRED: a blurred, low resolution image, encoded as a base64 data URI
TRACED_SVG: a low-resolution traced SVG of the image.
NONE: no placeholder. Set "background" to use a fixed background color.
layout
: The layout for the image.FIXED: A static image sized, that does not resize according to the screen width
FLUID: The image resizes to fit its container. Pass a "sizes" option if it isn't going to be the full width of the screen.
CONSTRAINED: Resizes to fit its container, up to a maximum width, at which point it will remain fixed in size.
outputPixelDensities
: A list of image pixel densities to generate, for high-resolution (retina) screens. It will never generate images larger than the source, and will always a 1x image.Default is [ 0.25, 0.5, 1, 2 ], for fluid/constrained images, and [ 1, 2 ] for fixed. In this case, an image with a fluid layout and maxWidth = 400 would generate images at 100, 200, 400 and 800px wide
sizes
: The "sizes" property, passed to the img tag. This describes the display size of the image.This does not affect the generated images, but is used by the browser to decide which images to download. You can leave this blank for fixed images, or if the responsive image
container will be the full width of the screen. In these cases we will generate an appropriate value.
This PR now also uses the same functions for static images, so they have a similar API.
[ch16945]
[ch16946]