From b096014043d7fffab7760ab9db531f9943d6b132 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 4 Feb 2021 12:09:22 +0000 Subject: [PATCH 01/11] feat(gatsby-plugin-image): Add url builder helper hook --- .../src/components/hooks.ts | 121 ++++++++++++++++++ .../gatsby-plugin-image/src/image-utils.ts | 10 +- .../gatsby-plugin-image/src/index.browser.ts | 3 + packages/gatsby-plugin-image/src/index.ts | 3 + 4 files changed, 135 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-plugin-image/src/components/hooks.ts b/packages/gatsby-plugin-image/src/components/hooks.ts index 276acd94daa46..cc4d1a848b003 100644 --- a/packages/gatsby-plugin-image/src/components/hooks.ts +++ b/packages/gatsby-plugin-image/src/components/hooks.ts @@ -19,6 +19,7 @@ import { generateImageData, Layout, EVERY_BREAKPOINT, + IImage, } from "../image-utils" const imageCache = new Set() @@ -96,6 +97,126 @@ export function useGatsbyImage({ return generateImageData({ pluginName, breakpoints, ...args }) } +export interface IUrlBuilderArgs { + width: number + height: number + baseUrl: string + options: OptionsType +} +export interface IUseUrlBuilderArgs { + baseUrl: string + /** + * For constrained and fixed images, the size of the image element + */ + width?: number + height?: number + /** + * If available, pass the source image width and height + */ + sourceWidth?: number + sourceHeight?: number + /** + * If only one dimension is passed, then this will be used to calculate the other + */ + aspectRatio?: number + layout?: Layout + /** + * Returns a URL based on the passed arguments. Should be a pure function + */ + urlBuilder: (args: IUrlBuilderArgs) => string + + /** + * Should be a data URI + */ + placeholderURL?: string + backgroundColor?: string + pluginName?: string + options?: OptionsType +} + +export function useImageUrlBuilder({ + baseUrl, + urlBuilder, + width, + height, + sourceWidth, + sourceHeight, + aspectRatio, + layout = `constrained`, + pluginName = `useImageUrlBuilder`, + backgroundColor, + placeholderURL, + options, +}: IUseUrlBuilderArgs): IGatsbyImageData { + const generateImageSource = ( + baseUrl: string, + width: number, + height?: number + ): IImage => { + return { + width, + height, + format: `auto`, + src: urlBuilder({ baseUrl, width, height, options }), + } + } + + const breakpoints = sourceWidth + ? EVERY_BREAKPOINT.filter(bp => bp <= sourceWidth) + : EVERY_BREAKPOINT + + let sourceMetadata: Partial = { + format: `auto`, + } + + if (sourceWidth && sourceHeight) { + sourceMetadata = { + width: sourceWidth, + height: sourceHeight, + format: `auto`, + } + if (!aspectRatio) { + aspectRatio = sourceWidth / sourceHeight + } + } + + if (layout === `fullWidth`) { + width = width || sourceWidth || breakpoints[breakpoints.length - 1] + height = height || aspectRatio ? width / aspectRatio : width / (4 / 3) + } else { + if (!width) { + if (height && aspectRatio) { + width = height * aspectRatio + } else if (sourceWidth) { + width = sourceWidth + } else if (height) { + width = height / (4 / 3) + } else { + width = 800 + } + } + + if (aspectRatio && !height) { + height = width / aspectRatio + } + } + + const args: IGatsbyImageHelperArgs = { + width, + height, + pluginName, + generateImageSource, + layout, + filename: baseUrl, + formats: [`auto`], + breakpoints, + backgroundColor, + placeholderURL, + sourceMetadata: sourceMetadata as IGatsbyImageHelperArgs["sourceMetadata"], + } + return useGatsbyImage(args) +} + export function getMainProps( isLoading: boolean, isLoaded: boolean, diff --git a/packages/gatsby-plugin-image/src/image-utils.ts b/packages/gatsby-plugin-image/src/image-utils.ts index 89ff7fe73f7fc..a8b1c22e35e77 100644 --- a/packages/gatsby-plugin-image/src/image-utils.ts +++ b/packages/gatsby-plugin-image/src/image-utils.ts @@ -107,6 +107,7 @@ export interface IGatsbyImageHelperArgs { fit?: Fit options?: Record breakpoints?: Array + backgroundColor?: string } const warn = (message: string): void => console.warn(message) @@ -164,6 +165,7 @@ export function generateImageData( height, filename, reporter = { warn }, + backgroundColor, } = args if (!pluginName) { @@ -180,7 +182,7 @@ export function generateImageData( sourceMetadata = { width, height, - format: formatFromFilename(filename), + format: sourceMetadata?.format || formatFromFilename(filename) || `auto`, } } else if (!sourceMetadata.format) { sourceMetadata.format = formatFromFilename(filename) @@ -262,7 +264,11 @@ export function generateImageData( } }) - const imageProps: Partial = { images: result, layout } + const imageProps: Partial = { + images: result, + layout, + backgroundColor, + } switch (layout) { case `fixed`: imageProps.width = imageSizes.presentationWidth diff --git a/packages/gatsby-plugin-image/src/index.browser.ts b/packages/gatsby-plugin-image/src/index.browser.ts index c3e2ff8c9843d..d7a441eb06735 100644 --- a/packages/gatsby-plugin-image/src/index.browser.ts +++ b/packages/gatsby-plugin-image/src/index.browser.ts @@ -12,7 +12,10 @@ export { getSrc, useGatsbyImage, useArtDirection, + useImageUrlBuilder, IArtDirectedImage, + IUseUrlBuilderArgs, + IUrlBuilderArgs, } from "./components/hooks" export { generateImageData, diff --git a/packages/gatsby-plugin-image/src/index.ts b/packages/gatsby-plugin-image/src/index.ts index 0f3ddbe216bf6..00fddec7b2ea2 100644 --- a/packages/gatsby-plugin-image/src/index.ts +++ b/packages/gatsby-plugin-image/src/index.ts @@ -10,8 +10,11 @@ export { getImage, getSrc, useGatsbyImage, + useImageUrlBuilder, useArtDirection, IArtDirectedImage, + IUseUrlBuilderArgs, + IUrlBuilderArgs, } from "./components/hooks" export { generateImageData, From 178da47e8c93caf601638eea630c167c7a24be3d Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 5 Feb 2021 13:53:26 +0000 Subject: [PATCH 02/11] Unify API --- .../src/components/hooks.ts | 50 +++++++++---------- .../gatsby-plugin-image/src/index.browser.ts | 3 +- packages/gatsby-plugin-image/src/index.ts | 3 +- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/packages/gatsby-plugin-image/src/components/hooks.ts b/packages/gatsby-plugin-image/src/components/hooks.ts index cc4d1a848b003..c19dabafcfacd 100644 --- a/packages/gatsby-plugin-image/src/components/hooks.ts +++ b/packages/gatsby-plugin-image/src/components/hooks.ts @@ -23,6 +23,8 @@ import { } from "../image-utils" const imageCache = new Set() +const DEFAULT_FIXED_WIDTH = 800 +const DEFAULT_ASPECT_RATIO = 4 / 3 // Native lazy-loading support: https://addyosmani.com/blog/lazy-loading/ export const hasNativeLazyLoadSupport = (): boolean => typeof HTMLImageElement !== `undefined` && @@ -89,21 +91,13 @@ export async function applyPolyfill( ;(window as any).objectFitPolyfill(ref.current) } -export function useGatsbyImage({ - pluginName = `useGatsbyImage`, - breakpoints = EVERY_BREAKPOINT, - ...args -}: IGatsbyImageHelperArgs): IGatsbyImageData { - return generateImageData({ pluginName, breakpoints, ...args }) -} - export interface IUrlBuilderArgs { width: number height: number baseUrl: string options: OptionsType } -export interface IUseUrlBuilderArgs { +export interface IUseGatsbyImageArgs { baseUrl: string /** * For constrained and fixed images, the size of the image element @@ -130,11 +124,20 @@ export interface IUseUrlBuilderArgs { */ placeholderURL?: string backgroundColor?: string + /** + * Used in error messages etc + */ pluginName?: string + /** + * Passed to the urlBuilder function + */ options?: OptionsType } -export function useImageUrlBuilder({ +/** + * Use this hook to generate gatsby-plugin-image data in the browser. + */ +export function useGatsbyImage({ baseUrl, urlBuilder, width, @@ -143,11 +146,11 @@ export function useImageUrlBuilder({ sourceHeight, aspectRatio, layout = `constrained`, - pluginName = `useImageUrlBuilder`, + pluginName = `useGatsbyImage`, backgroundColor, placeholderURL, options, -}: IUseUrlBuilderArgs): IGatsbyImageData { +}: IUseGatsbyImageArgs): IGatsbyImageData { const generateImageSource = ( baseUrl: string, width: number, @@ -165,24 +168,19 @@ export function useImageUrlBuilder({ ? EVERY_BREAKPOINT.filter(bp => bp <= sourceWidth) : EVERY_BREAKPOINT - let sourceMetadata: Partial = { + const sourceMetadata: IGatsbyImageHelperArgs["sourceMetadata"] = { + width: sourceWidth, + height: sourceHeight, format: `auto`, } - if (sourceWidth && sourceHeight) { - sourceMetadata = { - width: sourceWidth, - height: sourceHeight, - format: `auto`, - } - if (!aspectRatio) { - aspectRatio = sourceWidth / sourceHeight - } + if (sourceWidth && sourceHeight && !aspectRatio) { + aspectRatio = sourceWidth / sourceHeight } if (layout === `fullWidth`) { width = width || sourceWidth || breakpoints[breakpoints.length - 1] - height = height || aspectRatio ? width / aspectRatio : width / (4 / 3) + height = height || width / (aspectRatio || DEFAULT_ASPECT_RATIO) } else { if (!width) { if (height && aspectRatio) { @@ -192,7 +190,7 @@ export function useImageUrlBuilder({ } else if (height) { width = height / (4 / 3) } else { - width = 800 + width = DEFAULT_FIXED_WIDTH } } @@ -212,9 +210,9 @@ export function useImageUrlBuilder({ breakpoints, backgroundColor, placeholderURL, - sourceMetadata: sourceMetadata as IGatsbyImageHelperArgs["sourceMetadata"], + sourceMetadata, } - return useGatsbyImage(args) + return generateImageData(args) } export function getMainProps( diff --git a/packages/gatsby-plugin-image/src/index.browser.ts b/packages/gatsby-plugin-image/src/index.browser.ts index d7a441eb06735..25b9b82b2a296 100644 --- a/packages/gatsby-plugin-image/src/index.browser.ts +++ b/packages/gatsby-plugin-image/src/index.browser.ts @@ -12,9 +12,8 @@ export { getSrc, useGatsbyImage, useArtDirection, - useImageUrlBuilder, IArtDirectedImage, - IUseUrlBuilderArgs, + IUseGatsbyImageArgs, IUrlBuilderArgs, } from "./components/hooks" export { diff --git a/packages/gatsby-plugin-image/src/index.ts b/packages/gatsby-plugin-image/src/index.ts index 00fddec7b2ea2..dda22dcd2f1ad 100644 --- a/packages/gatsby-plugin-image/src/index.ts +++ b/packages/gatsby-plugin-image/src/index.ts @@ -10,10 +10,9 @@ export { getImage, getSrc, useGatsbyImage, - useImageUrlBuilder, useArtDirection, IArtDirectedImage, - IUseUrlBuilderArgs, + IUseGatsbyImageArgs, IUrlBuilderArgs, } from "./components/hooks" export { From 4b5465e41712d891e62fae33cfe6db80519d03f5 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 5 Feb 2021 16:38:24 +0000 Subject: [PATCH 03/11] Refactor --- .../src/components/hooks.ts | 70 ++++++------------- .../gatsby-plugin-image/src/image-utils.ts | 41 ++++++++++- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/packages/gatsby-plugin-image/src/components/hooks.ts b/packages/gatsby-plugin-image/src/components/hooks.ts index c19dabafcfacd..47b3d4d57b54f 100644 --- a/packages/gatsby-plugin-image/src/components/hooks.ts +++ b/packages/gatsby-plugin-image/src/components/hooks.ts @@ -20,11 +20,11 @@ import { Layout, EVERY_BREAKPOINT, IImage, + ImageFormat, } from "../image-utils" +import { PictureProps } from "./picture" const imageCache = new Set() -const DEFAULT_FIXED_WIDTH = 800 -const DEFAULT_ASPECT_RATIO = 4 / 3 // Native lazy-loading support: https://addyosmani.com/blog/lazy-loading/ export const hasNativeLazyLoadSupport = (): boolean => typeof HTMLImageElement !== `undefined` && @@ -95,6 +95,7 @@ export interface IUrlBuilderArgs { width: number height: number baseUrl: string + format: ImageFormat options: OptionsType } export interface IUseGatsbyImageArgs { @@ -110,7 +111,7 @@ export interface IUseGatsbyImageArgs { sourceWidth?: number sourceHeight?: number /** - * If only one dimension is passed, then this will be used to calculate the other + * If only one dimension is passed, then this will be used to calculate the other. */ aspectRatio?: number layout?: Layout @@ -128,6 +129,14 @@ export interface IUseGatsbyImageArgs { * Used in error messages etc */ pluginName?: string + + /** + * If you do not support auto-format, pass an array of image types here + */ + formats?: Array + + breakpoints?: Array + /** * Passed to the urlBuilder function */ @@ -140,76 +149,41 @@ export interface IUseGatsbyImageArgs { export function useGatsbyImage({ baseUrl, urlBuilder, - width, - height, sourceWidth, sourceHeight, - aspectRatio, - layout = `constrained`, pluginName = `useGatsbyImage`, - backgroundColor, - placeholderURL, + formats = [`auto`], + breakpoints = EVERY_BREAKPOINT, options, + ...props }: IUseGatsbyImageArgs): IGatsbyImageData { const generateImageSource = ( baseUrl: string, width: number, - height?: number + height?: number, + format?: ImageFormat ): IImage => { return { width, height, - format: `auto`, - src: urlBuilder({ baseUrl, width, height, options }), + format, + src: urlBuilder({ baseUrl, width, height, options, format }), } } - const breakpoints = sourceWidth - ? EVERY_BREAKPOINT.filter(bp => bp <= sourceWidth) - : EVERY_BREAKPOINT - const sourceMetadata: IGatsbyImageHelperArgs["sourceMetadata"] = { width: sourceWidth, height: sourceHeight, format: `auto`, } - if (sourceWidth && sourceHeight && !aspectRatio) { - aspectRatio = sourceWidth / sourceHeight - } - - if (layout === `fullWidth`) { - width = width || sourceWidth || breakpoints[breakpoints.length - 1] - height = height || width / (aspectRatio || DEFAULT_ASPECT_RATIO) - } else { - if (!width) { - if (height && aspectRatio) { - width = height * aspectRatio - } else if (sourceWidth) { - width = sourceWidth - } else if (height) { - width = height / (4 / 3) - } else { - width = DEFAULT_FIXED_WIDTH - } - } - - if (aspectRatio && !height) { - height = width / aspectRatio - } - } - const args: IGatsbyImageHelperArgs = { - width, - height, + ...props, pluginName, generateImageSource, - layout, filename: baseUrl, - formats: [`auto`], + formats, breakpoints, - backgroundColor, - placeholderURL, sourceMetadata, } return generateImageData(args) @@ -218,7 +192,7 @@ export function useGatsbyImage({ export function getMainProps( isLoading: boolean, isLoaded: boolean, - images: any, + images: Pick, loading?: "eager" | "lazy", toggleLoaded?: (loaded: boolean) => void, cacheKey?: string, diff --git a/packages/gatsby-plugin-image/src/image-utils.ts b/packages/gatsby-plugin-image/src/image-utils.ts index a8b1c22e35e77..e47ae985f553a 100644 --- a/packages/gatsby-plugin-image/src/image-utils.ts +++ b/packages/gatsby-plugin-image/src/image-utils.ts @@ -19,7 +19,8 @@ export const EVERY_BREAKPOINT = [ 4096, ] const DEFAULT_FLUID_WIDTH = 800 -const DEFAULT_FIXED_WIDTH = 400 +const DEFAULT_FIXED_WIDTH = 800 +const DEFAULT_ASPECT_RATIO = 4 / 3 export type Fit = "cover" | "fill" | "inside" | "outside" | "contain" @@ -108,6 +109,7 @@ export interface IGatsbyImageHelperArgs { options?: Record breakpoints?: Array backgroundColor?: string + aspectRatio?: number } const warn = (message: string): void => console.warn(message) @@ -151,9 +153,46 @@ export function formatFromFilename(filename: string): ImageFormat | undefined { return undefined } +export function setDefaultDimensions( + args: IGatsbyImageHelperArgs +): IGatsbyImageHelperArgs { + let { layout, width, height, sourceMetadata, breakpoints, aspectRatio } = args + + if (width && height) { + return args + } + if (sourceMetadata.width && sourceMetadata.height && !aspectRatio) { + aspectRatio = sourceMetadata.width / sourceMetadata.height + } + + if (layout === `fullWidth`) { + width = width || sourceMetadata.width || breakpoints[breakpoints.length - 1] + height = height || width / (aspectRatio || DEFAULT_ASPECT_RATIO) + } else { + if (!width) { + if (height && aspectRatio) { + width = height * aspectRatio + } else if (sourceMetadata.width) { + width = sourceMetadata.width + } else if (height) { + width = height / (4 / 3) + } else { + width = DEFAULT_FIXED_WIDTH + } + } + + if (aspectRatio && !height) { + height = width / aspectRatio + } + } + return { ...args, width, height, aspectRatio } +} + export function generateImageData( args: IGatsbyImageHelperArgs ): IGatsbyImageData { + args = setDefaultDimensions(args) + let { pluginName, sourceMetadata, From 0ac0e10d493bf5497a2bd59ee9a780bc2ef22237 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Mon, 8 Feb 2021 09:31:28 +0000 Subject: [PATCH 04/11] Fix types --- packages/gatsby-plugin-image/src/components/hooks.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/gatsby-plugin-image/src/components/hooks.ts b/packages/gatsby-plugin-image/src/components/hooks.ts index 47b3d4d57b54f..361aa70eaef68 100644 --- a/packages/gatsby-plugin-image/src/components/hooks.ts +++ b/packages/gatsby-plugin-image/src/components/hooks.ts @@ -22,7 +22,6 @@ import { IImage, ImageFormat, } from "../image-utils" -import { PictureProps } from "./picture" const imageCache = new Set() // Native lazy-loading support: https://addyosmani.com/blog/lazy-loading/ @@ -192,13 +191,13 @@ export function useGatsbyImage({ export function getMainProps( isLoading: boolean, isLoaded: boolean, - images: Pick, + images: IGatsbyImageData["images"], loading?: "eager" | "lazy", toggleLoaded?: (loaded: boolean) => void, cacheKey?: string, ref?: RefObject, style: CSSProperties = {} -): MainImageProps { +): Partial { const onLoad: ReactEventHandler = function (e) { if (isLoaded) { return From 79e38aa3462a2b808aaeacda3cae08f592173897 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 9 Feb 2021 15:45:35 +0000 Subject: [PATCH 05/11] Add resolver --- .../gatsby-plugin-image/src/image-utils.ts | 11 ++- .../gatsby-plugin-image/src/resolver-utils.ts | 81 ++++++++++++++++++- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/packages/gatsby-plugin-image/src/image-utils.ts b/packages/gatsby-plugin-image/src/image-utils.ts index e47ae985f553a..ddc7cb981b5e0 100644 --- a/packages/gatsby-plugin-image/src/image-utils.ts +++ b/packages/gatsby-plugin-image/src/image-utils.ts @@ -1,5 +1,6 @@ /* eslint-disable no-unused-expressions */ import { stripIndent } from "common-tags" +import camelCase from "lodash/camelcase" import { IGatsbyImageData } from "." const DEFAULT_PIXEL_DENSITIES = [0.25, 0.5, 1, 2] @@ -205,6 +206,7 @@ export function generateImageData( filename, reporter = { warn }, backgroundColor, + formats: rawFormats = [`auto`, `webp`], } = args if (!pluginName) { @@ -216,6 +218,9 @@ export function generateImageData( if (typeof generateImageSource !== `function`) { throw new Error(`generateImageSource must be a function`) } + + layout = camelCase(layout) as Layout + if (!sourceMetadata || (!sourceMetadata.width && !sourceMetadata.height)) { // No metadata means we let the CDN handle max size etc, aspect ratio etc sourceMetadata = { @@ -226,8 +231,10 @@ export function generateImageData( } else if (!sourceMetadata.format) { sourceMetadata.format = formatFromFilename(filename) } - // - const formats = new Set(args.formats || [`auto`, `webp`]) + + const formats = new Set( + rawFormats.map(format => format.toLowerCase() as ImageFormat) + ) if (formats.size === 0 || formats.has(`auto`) || formats.has(``)) { formats.delete(`auto`) diff --git a/packages/gatsby-plugin-image/src/resolver-utils.ts b/packages/gatsby-plugin-image/src/resolver-utils.ts index 84f6fb1995bb5..fa27bc4630f25 100644 --- a/packages/gatsby-plugin-image/src/resolver-utils.ts +++ b/packages/gatsby-plugin-image/src/resolver-utils.ts @@ -43,6 +43,72 @@ export const ImagePlaceholderType = new GraphQLEnumType({ }, }) +export interface GatsbyGraphQLFieldConfig { + description?: string + type: string + args?: Record + resolve: GraphQLFieldResolver +} +export interface GatsbyGraphQLResolverArgumentConfig { + description?: string + type: string | Array + defaultValue?: TValue +} + +export function getGatsbyImageResolver( + resolve: GraphQLFieldResolver, + extraArgs?: Record +): GatsbyGraphQLFieldConfig { + return { + type: `JSON!`, + args: { + layout: { + type: `enum GatsbyImageLayout { FIXED, FULL_WIDTH, CONSTRAINED }`, + description: stripIndent` + The layout for the image. + FIXED: A static image sized, that does not resize according to the screen width + FULL_WIDTH: 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. + `, + }, + width: { + type: `Int`, + description: stripIndent` + The display width of the generated image for layout = FIXED, and the display width of the largest image for layout = CONSTRAINED. + The actual largest image resolution will be this value multiplied by the largest value in outputPixelDensities + Ignored if layout = FULL_WIDTH. + `, + }, + height: { + type: `Int`, + description: stripIndent` + If set, the height of the generated image. If omitted, it is calculated from the supplied width, matching the aspect ratio of the source image.`, + }, + aspectRatio: { + type: `Float`, + description: stripIndent` + If set along with width or height, this will set the value of the other dimension to match the provided aspect ratio, cropping the image if needed. + If neither width or height is provided, height will be set based on the intrinsic width of the source image. + `, + }, + sizes: { + type: `String`, + description: stripIndent` + 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. + `, + }, + backgroundColor: { + type: `String`, + description: `Background color applied to the wrapper. Also passed to sharp to use as a background when "letterboxing" an image to another aspect ratio.`, + }, + ...extraArgs, + }, + resolve, + } +} + export function getGatsbyImageFieldConfig( resolve: GraphQLFieldResolver, extraArgs?: GraphQLFieldConfigArgumentMap @@ -52,7 +118,6 @@ export function getGatsbyImageFieldConfig( args: { layout: { type: ImageLayoutType, - defaultValue: `constrained`, description: stripIndent` The layout for the image. FIXED: A static image sized, that does not resize according to the screen width @@ -73,9 +138,15 @@ export function getGatsbyImageFieldConfig( description: stripIndent` If set, the height of the generated image. If omitted, it is calculated from the supplied width, matching the aspect ratio of the source image.`, }, + aspectRatio: { + type: GraphQLFloat, + description: stripIndent` + If set along with width or height, this will set the value of the other dimension to match the provided aspect ratio, cropping the image if needed. + If neither width or height is provided, height will be set based on the intrinsic width of the source image. + `, + }, placeholder: { type: ImagePlaceholderType, - defaultValue: `blurred`, description: stripIndent` Format of generated placeholder image, displayed while the main image loads. BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) @@ -91,7 +162,6 @@ export function getGatsbyImageFieldConfig( not know the formats of the source images, as this could lead to unwanted results such as converting JPEGs to PNGs. Specifying both PNG and JPG is not supported and will be ignored. AVIF support is currently experimental. `, - defaultValue: [`auto`, `webp`], }, outputPixelDensities: { type: GraphQLList(GraphQLFloat), @@ -102,13 +172,16 @@ export function getGatsbyImageFieldConfig( }, sizes: { type: GraphQLString, - defaultValue: ``, description: stripIndent` 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. `, }, + backgroundColor: { + type: GraphQLString, + description: `Background color applied to the wrapper. Also passed to sharp to use as a background when "letterboxing" an image to another aspect ratio.`, + }, ...extraArgs, }, resolve, From 2450324fc476a62e62d96b91cfbd797c9ec05270 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 9 Feb 2021 15:59:07 +0000 Subject: [PATCH 06/11] Add breakpoints --- .../gatsby-plugin-image/src/resolver-utils.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-plugin-image/src/resolver-utils.ts b/packages/gatsby-plugin-image/src/resolver-utils.ts index 267b20e5ae328..2ce7aa66b39d3 100644 --- a/packages/gatsby-plugin-image/src/resolver-utils.ts +++ b/packages/gatsby-plugin-image/src/resolver-utils.ts @@ -99,6 +99,22 @@ export function getGatsbyImageResolver( container will be the full width of the screen. In these cases we will generate an appropriate value. `, }, + outputPixelDensities: { + type: `[Float]`, + description: stripIndent` + A list of image pixel densities to generate for FIXED and CONSTRAINED images. You should rarely need to change this. It will never generate images larger than the source, and will always include 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. + Ignored for FULL_WIDTH, which uses breakpoints instead. + `, + }, + breakpoints: { + type: `[Int]`, + description: stripIndent` + Specifies the image widths to generate. You should rarely need to change this. For FIXED and CONSTRAINED images it is better to allow these to be determined automatically, + based on the image size. For FULL_WIDTH images this can be used to override the default, which is determined by the plugin. + It will never generate any images larger than the source. + `, + }, backgroundColor: { type: `String`, description: `Background color applied to the wrapper. Also passed to sharp to use as a background when "letterboxing" an image to another aspect ratio.`, @@ -167,10 +183,18 @@ export function getGatsbyImageFieldConfig( outputPixelDensities: { type: GraphQLList(GraphQLFloat), description: stripIndent` - A list of image pixel densities to generate. It will never generate images larger than the source, and will always include 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 + A list of image pixel densities to generate for FIXED and CONSTRAINED images. You should rarely need to change this. It will never generate images larger than the source, and will always include 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. `, }, + breakpoints: { + type: GraphQLList(GraphQLInt), + description: stripIndent` + Specifies the image widths to generate. You should rarely need to change this. For FIXED and CONSTRAINED images it is better to allow these to be determined automatically, + based on the image size. For FULL_WIDTH images this can be used to override the default, which is [750, 1080, 1366, 1920]. + It will never generate any images larger than the source. + `, + }, sizes: { type: GraphQLString, description: stripIndent` From 07e3b0e01da509e1215e4d995efa3f2c0c7528f3 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 9 Feb 2021 16:07:43 +0000 Subject: [PATCH 07/11] Add background color --- packages/gatsby-plugin-image/src/resolver-utils.ts | 4 ++-- packages/gatsby-source-contentful/src/extend-node-type.js | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/gatsby-plugin-image/src/resolver-utils.ts b/packages/gatsby-plugin-image/src/resolver-utils.ts index 2ce7aa66b39d3..bf9bc47ceac3d 100644 --- a/packages/gatsby-plugin-image/src/resolver-utils.ts +++ b/packages/gatsby-plugin-image/src/resolver-utils.ts @@ -117,7 +117,7 @@ export function getGatsbyImageResolver( }, backgroundColor: { type: `String`, - description: `Background color applied to the wrapper. Also passed to sharp to use as a background when "letterboxing" an image to another aspect ratio.`, + description: `Background color applied to the wrapper, or when "letterboxing" an image to another aspect ratio.`, }, ...extraArgs, }, @@ -205,7 +205,7 @@ export function getGatsbyImageFieldConfig( }, backgroundColor: { type: GraphQLString, - description: `Background color applied to the wrapper. Also passed to sharp to use as a background when "letterboxing" an image to another aspect ratio.`, + description: `Background color applied to the wrapper, or when "letterboxing" an image to another aspect ratio.`, }, ...extraArgs, }, diff --git a/packages/gatsby-source-contentful/src/extend-node-type.js b/packages/gatsby-source-contentful/src/extend-node-type.js index 04cbd59057d70..42e51936c7953 100644 --- a/packages/gatsby-source-contentful/src/extend-node-type.js +++ b/packages/gatsby-source-contentful/src/extend-node-type.js @@ -775,9 +775,6 @@ exports.extendNodeType = ({ type, store, reporter }) => { type: GraphQLInt, defaultValue: 50, }, - backgroundColor: { - type: GraphQLString, - }, }) } From e30a56a094188cabdcaee2b8b4a79268170e2ca2 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 9 Feb 2021 16:25:17 +0000 Subject: [PATCH 08/11] lint --- packages/gatsby-plugin-image/src/resolver-utils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/gatsby-plugin-image/src/resolver-utils.ts b/packages/gatsby-plugin-image/src/resolver-utils.ts index bf9bc47ceac3d..0f471d4c8b8dd 100644 --- a/packages/gatsby-plugin-image/src/resolver-utils.ts +++ b/packages/gatsby-plugin-image/src/resolver-utils.ts @@ -43,13 +43,13 @@ export const ImagePlaceholderType = new GraphQLEnumType({ }, }) -export interface GatsbyGraphQLFieldConfig { +export interface IGatsbyGraphQLFieldConfig { description?: string type: string - args?: Record + args?: Record resolve: GraphQLFieldResolver } -export interface GatsbyGraphQLResolverArgumentConfig { +export interface IGatsbyGraphQLResolverArgumentConfig { description?: string type: string | Array defaultValue?: TValue @@ -57,8 +57,8 @@ export interface GatsbyGraphQLResolverArgumentConfig { export function getGatsbyImageResolver( resolve: GraphQLFieldResolver, - extraArgs?: Record -): GatsbyGraphQLFieldConfig { + extraArgs?: Record +): IGatsbyGraphQLFieldConfig { return { type: `JSON!`, args: { From 60c3d722598351a43f25bb24bbee583980fe7ed8 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 9 Feb 2021 16:53:01 +0000 Subject: [PATCH 09/11] Fix default handling --- .../gatsby-plugin-image/src/image-utils.ts | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/gatsby-plugin-image/src/image-utils.ts b/packages/gatsby-plugin-image/src/image-utils.ts index ddc7cb981b5e0..12fbc189ab998 100644 --- a/packages/gatsby-plugin-image/src/image-utils.ts +++ b/packages/gatsby-plugin-image/src/image-utils.ts @@ -157,7 +157,17 @@ export function formatFromFilename(filename: string): ImageFormat | undefined { export function setDefaultDimensions( args: IGatsbyImageHelperArgs ): IGatsbyImageHelperArgs { - let { layout, width, height, sourceMetadata, breakpoints, aspectRatio } = args + let { + layout = `constrained`, + width, + height, + sourceMetadata, + breakpoints, + aspectRatio, + formats = [`auto`, `webp`], + } = args + formats = formats.map(format => format.toLowerCase() as ImageFormat) + layout = camelCase(layout) as Layout if (width && height) { return args @@ -168,7 +178,7 @@ export function setDefaultDimensions( if (layout === `fullWidth`) { width = width || sourceMetadata.width || breakpoints[breakpoints.length - 1] - height = height || width / (aspectRatio || DEFAULT_ASPECT_RATIO) + height = height || Math.round(width / (aspectRatio || DEFAULT_ASPECT_RATIO)) } else { if (!width) { if (height && aspectRatio) { @@ -176,17 +186,17 @@ export function setDefaultDimensions( } else if (sourceMetadata.width) { width = sourceMetadata.width } else if (height) { - width = height / (4 / 3) + width = Math.round(height / (4 / 3)) } else { width = DEFAULT_FIXED_WIDTH } } if (aspectRatio && !height) { - height = width / aspectRatio + height = Math.round(width / aspectRatio) } } - return { ...args, width, height, aspectRatio } + return { ...args, width, height, aspectRatio, layout, formats } } export function generateImageData( @@ -198,7 +208,7 @@ export function generateImageData( pluginName, sourceMetadata, generateImageSource, - layout = `constrained`, + layout, fit, options, width, @@ -206,7 +216,6 @@ export function generateImageData( filename, reporter = { warn }, backgroundColor, - formats: rawFormats = [`auto`, `webp`], } = args if (!pluginName) { @@ -219,8 +228,6 @@ export function generateImageData( throw new Error(`generateImageSource must be a function`) } - layout = camelCase(layout) as Layout - if (!sourceMetadata || (!sourceMetadata.width && !sourceMetadata.height)) { // No metadata means we let the CDN handle max size etc, aspect ratio etc sourceMetadata = { @@ -232,9 +239,7 @@ export function generateImageData( sourceMetadata.format = formatFromFilename(filename) } - const formats = new Set( - rawFormats.map(format => format.toLowerCase() as ImageFormat) - ) + const formats = new Set(args.formats) if (formats.size === 0 || formats.has(`auto`) || formats.has(``)) { formats.delete(`auto`) @@ -369,7 +374,7 @@ export function calculateImageSizes(args: IImageSizeArgs): IImageSizes { return responsiveImageSizes({ breakpoints, ...args }) } else { reporter.warn( - `No valid layout was provided for the image at ${filename}. Valid image layouts are fixed, fullWidth, and constrained.` + `No valid layout was provided for the image at ${filename}. Valid image layouts are fixed, fullWidth, and constrained. Found ${layout}` ) return { sizes: [imgDimensions.width], From 304224366c8d29ee2d4c06903630b5bf3f64d912 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 9 Feb 2021 16:57:14 +0000 Subject: [PATCH 10/11] Fix import --- packages/gatsby-plugin-image/src/image-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-plugin-image/src/image-utils.ts b/packages/gatsby-plugin-image/src/image-utils.ts index 12fbc189ab998..3a0d3d3834720 100644 --- a/packages/gatsby-plugin-image/src/image-utils.ts +++ b/packages/gatsby-plugin-image/src/image-utils.ts @@ -1,6 +1,6 @@ /* eslint-disable no-unused-expressions */ import { stripIndent } from "common-tags" -import camelCase from "lodash/camelcase" +import camelCase from "camelcase" import { IGatsbyImageData } from "." const DEFAULT_PIXEL_DENSITIES = [0.25, 0.5, 1, 2] From 8ee08f05f4c4415e8e3a3c63d8edc1bcba19486b Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 10 Feb 2021 16:09:35 +0000 Subject: [PATCH 11/11] Apply suggestions from code review Co-authored-by: LB --- packages/gatsby-plugin-image/src/image-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-plugin-image/src/image-utils.ts b/packages/gatsby-plugin-image/src/image-utils.ts index 3a0d3d3834720..9c7db11837caa 100644 --- a/packages/gatsby-plugin-image/src/image-utils.ts +++ b/packages/gatsby-plugin-image/src/image-utils.ts @@ -186,7 +186,7 @@ export function setDefaultDimensions( } else if (sourceMetadata.width) { width = sourceMetadata.width } else if (height) { - width = Math.round(height / (4 / 3)) + width = Math.round(height / DEFAULT_ASPECT_RATIO) } else { width = DEFAULT_FIXED_WIDTH }