Skip to content

Commit

Permalink
feat(gatsby-plugin-image): Add resolver helper and improve custom hook (
Browse files Browse the repository at this point in the history
#29342)

* feat(gatsby-plugin-image): Add url builder helper hook

* Unify API

* Refactor

* Fix types

* Add resolver

* Add breakpoints

* Add background color

* lint

* Fix default handling

* Fix import

* Apply suggestions from code review

Co-authored-by: LB <laurie@gatsbyjs.com>

Co-authored-by: gatsbybot <mathews.kyle+gatsbybot@gmail.com>
Co-authored-by: LB <laurie@gatsbyjs.com>
  • Loading branch information
3 people committed Feb 10, 2021
1 parent 01b6123 commit e567aa8
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 23 deletions.
104 changes: 98 additions & 6 deletions packages/gatsby-plugin-image/src/components/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
generateImageData,
Layout,
EVERY_BREAKPOINT,
IImage,
ImageFormat,
} from "../image-utils"
const imageCache = new Set<string>()

Expand Down Expand Up @@ -88,24 +90,114 @@ export async function applyPolyfill(
;(window as any).objectFitPolyfill(ref.current)
}

export function useGatsbyImage({
export interface IUrlBuilderArgs<OptionsType> {
width: number
height: number
baseUrl: string
format: ImageFormat
options: OptionsType
}
export interface IUseGatsbyImageArgs<OptionsType = {}> {
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<OptionsType>) => string

/**
* Should be a data URI
*/
placeholderURL?: string
backgroundColor?: string
/**
* Used in error messages etc
*/
pluginName?: string

/**
* If you do not support auto-format, pass an array of image types here
*/
formats?: Array<ImageFormat>

breakpoints?: Array<number>

/**
* Passed to the urlBuilder function
*/
options?: OptionsType
}

/**
* Use this hook to generate gatsby-plugin-image data in the browser.
*/
export function useGatsbyImage<OptionsType>({
baseUrl,
urlBuilder,
sourceWidth,
sourceHeight,
pluginName = `useGatsbyImage`,
formats = [`auto`],
breakpoints = EVERY_BREAKPOINT,
...args
}: IGatsbyImageHelperArgs): IGatsbyImageData {
return generateImageData({ pluginName, breakpoints, ...args })
options,
...props
}: IUseGatsbyImageArgs<OptionsType>): IGatsbyImageData {
const generateImageSource = (
baseUrl: string,
width: number,
height?: number,
format?: ImageFormat
): IImage => {
return {
width,
height,
format,
src: urlBuilder({ baseUrl, width, height, options, format }),
}
}

const sourceMetadata: IGatsbyImageHelperArgs["sourceMetadata"] = {
width: sourceWidth,
height: sourceHeight,
format: `auto`,
}

const args: IGatsbyImageHelperArgs = {
...props,
pluginName,
generateImageSource,
filename: baseUrl,
formats,
breakpoints,
sourceMetadata,
}
return generateImageData(args)
}

export function getMainProps(
isLoading: boolean,
isLoaded: boolean,
images: any,
images: IGatsbyImageData["images"],
loading?: "eager" | "lazy",
toggleLoaded?: (loaded: boolean) => void,
cacheKey?: string,
ref?: RefObject<HTMLImageElement>,
style: CSSProperties = {}
): MainImageProps {
): Partial<MainImageProps> {
const onLoad: ReactEventHandler<HTMLImageElement> = function (e) {
if (isLoaded) {
return
Expand Down
71 changes: 64 additions & 7 deletions packages/gatsby-plugin-image/src/image-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable no-unused-expressions */
import { stripIndent } from "common-tags"
import camelCase from "camelcase"
import { IGatsbyImageData } from "."

const DEFAULT_PIXEL_DENSITIES = [0.25, 0.5, 1, 2]
Expand All @@ -19,7 +20,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"

Expand Down Expand Up @@ -107,6 +109,8 @@ export interface IGatsbyImageHelperArgs {
fit?: Fit
options?: Record<string, unknown>
breakpoints?: Array<number>
backgroundColor?: string
aspectRatio?: number
}

const warn = (message: string): void => console.warn(message)
Expand Down Expand Up @@ -150,20 +154,68 @@ export function formatFromFilename(filename: string): ImageFormat | undefined {
return undefined
}

export function setDefaultDimensions(
args: IGatsbyImageHelperArgs
): IGatsbyImageHelperArgs {
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
}
if (sourceMetadata.width && sourceMetadata.height && !aspectRatio) {
aspectRatio = sourceMetadata.width / sourceMetadata.height
}

if (layout === `fullWidth`) {
width = width || sourceMetadata.width || breakpoints[breakpoints.length - 1]
height = height || Math.round(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 = Math.round(height / DEFAULT_ASPECT_RATIO)
} else {
width = DEFAULT_FIXED_WIDTH
}
}

if (aspectRatio && !height) {
height = Math.round(width / aspectRatio)
}
}
return { ...args, width, height, aspectRatio, layout, formats }
}

export function generateImageData(
args: IGatsbyImageHelperArgs
): IGatsbyImageData {
args = setDefaultDimensions(args)

let {
pluginName,
sourceMetadata,
generateImageSource,
layout = `constrained`,
layout,
fit,
options,
width,
height,
filename,
reporter = { warn },
backgroundColor,
} = args

if (!pluginName) {
Expand All @@ -175,18 +227,19 @@ export function generateImageData(
if (typeof generateImageSource !== `function`) {
throw new Error(`generateImageSource must be a function`)
}

if (!sourceMetadata || (!sourceMetadata.width && !sourceMetadata.height)) {
// No metadata means we let the CDN handle max size etc, aspect ratio etc
sourceMetadata = {
width,
height,
format: formatFromFilename(filename),
format: sourceMetadata?.format || formatFromFilename(filename) || `auto`,
}
} else if (!sourceMetadata.format) {
sourceMetadata.format = formatFromFilename(filename)
}
//
const formats = new Set<ImageFormat>(args.formats || [`auto`, `webp`])

const formats = new Set<ImageFormat>(args.formats)

if (formats.size === 0 || formats.has(`auto`) || formats.has(``)) {
formats.delete(`auto`)
Expand Down Expand Up @@ -262,7 +315,11 @@ export function generateImageData(
}
})

const imageProps: Partial<IGatsbyImageData> = { images: result, layout }
const imageProps: Partial<IGatsbyImageData> = {
images: result,
layout,
backgroundColor,
}
switch (layout) {
case `fixed`:
imageProps.width = imageSizes.presentationWidth
Expand Down Expand Up @@ -317,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],
Expand Down
2 changes: 2 additions & 0 deletions packages/gatsby-plugin-image/src/index.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export {
useGatsbyImage,
useArtDirection,
IArtDirectedImage,
IUseGatsbyImageArgs,
IUrlBuilderArgs,
} from "./components/hooks"
export {
generateImageData,
Expand Down
2 changes: 2 additions & 0 deletions packages/gatsby-plugin-image/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export {
useGatsbyImage,
useArtDirection,
IArtDirectedImage,
IUseGatsbyImageArgs,
IUrlBuilderArgs,
} from "./components/hooks"
export {
generateImageData,
Expand Down

0 comments on commit e567aa8

Please sign in to comment.