Skip to content

Commit 2058775

Browse files
authored
feat: Add AVIF image support to beta image plugin (#28742)
* Upgrade sharp * Add avif support * Add avif e2e tests * Allow - but warn about - using just AVIF * Add missing AVIF gql type * Upgrade e2e test executor
1 parent 146b197 commit 2058775

21 files changed

+133
-20
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ executors:
2323
aliases:
2424
e2e-executor: &e2e-executor
2525
docker:
26-
- image: cypress/browsers:node10.16.0-chrome76
26+
- image: cypress/browsers:node12.18.3-chrome87-ff82
2727

2828
restore_cache: &restore_cache
2929
restore_cache:

e2e-tests/visual-regression/cypress/integration/image.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ const testCases = [
33
["fixed image smaller than requested size", "/images/fixed-too-big"],
44
["fluid image", "/images/fluid"],
55
["constrained image", "/images/constrained"],
6+
["avif format", "/images/avif"],
67
]
78
const staticImageTestCases = [
89
["fixed image", "/static-images/fixed"],
910
["fixed image smaller than requested size", "/static-images/fixed-too-big"],
1011
["fluid image", "/static-images/fluid"],
1112
["constrained image", "/static-images/constrained"],
13+
["avif format", "/static-images/avif"],
1214
]
1315

1416
const sizes = [["iphone-6"], ["ipad-2"], [1027, 768]]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from "react"
2+
import { GatsbyImage, getImage } from "gatsby-plugin-image"
3+
import { useStaticQuery, graphql } from "gatsby"
4+
import { TestWrapper } from "../../components/test-wrapper"
5+
import Layout from "../../components/layout"
6+
7+
const Page = () => {
8+
const data = useStaticQuery(graphql`
9+
query {
10+
file(relativePath: { eq: "cornwall.jpg" }) {
11+
childImageSharp {
12+
gatsbyImageData(maxWidth: 1024, layout: CONSTRAINED, formats: [AVIF])
13+
}
14+
}
15+
}
16+
`)
17+
18+
return (
19+
<Layout>
20+
<h1>AVIF</h1>
21+
<TestWrapper>
22+
<GatsbyImage image={getImage(data.file)} alt="cornwall" />
23+
</TestWrapper>
24+
</Layout>
25+
)
26+
}
27+
28+
export default Page
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as React from "react"
2+
import { StaticImage } from "gatsby-plugin-image"
3+
import { TestWrapper } from "../../components/test-wrapper"
4+
import Layout from "../../components/layout"
5+
6+
const Page = () => {
7+
return (
8+
<Layout>
9+
<h1>AVIF</h1>
10+
<TestWrapper>
11+
<StaticImage
12+
src="../../images/cornwall.jpg"
13+
alt="cornwall"
14+
formats={["avif"]}
15+
maxWidth={1024}
16+
/>
17+
</TestWrapper>
18+
</Layout>
19+
)
20+
}
21+
22+
export default Page

packages/gatsby-plugin-image/src/babel-helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const SHARP_ATTRIBUTES = new Set([
1111
`maxWidth`,
1212
`maxHeight`,
1313
`quality`,
14+
`avifOptions`,
1415
`jpegOptions`,
1516
`pngOptions`,
1617
`webpOptions`,

packages/gatsby-plugin-image/src/components/static-image.server.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface IStaticImageProps extends Omit<GatsbyImageProps, "image"> {
2222
jpgOptions?: Record<string, unknown>
2323
pngOptions?: Record<string, unknown>
2424
webpOptions?: Record<string, unknown>
25+
avifOptions?: Record<string, unknown>
2526
blurredOptions?: Record<string, unknown>
2627
}
2728

packages/gatsby-plugin-image/src/resolver-utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const ImageFormatType = new GraphQLEnumType({
2020
JPG: { value: `jpg` },
2121
PNG: { value: `png` },
2222
WEBP: { value: `webp` },
23+
AVIF: { value: `avif` },
2324
},
2425
})
2526

@@ -99,10 +100,10 @@ export function getGatsbyImageFieldConfig<TSource, TContext>(
99100
formats: {
100101
type: GraphQLList(ImageFormatType),
101102
description: stripIndent`
102-
The image formats to generate. Valid values are "AUTO" (meaning the same format as the source image), "JPG", "PNG" and "WEBP".
103+
The image formats to generate. Valid values are AUTO (meaning the same format as the source image), JPG, PNG, WEBP and AVIF.
103104
The default value is [AUTO, WEBP], and you should rarely need to change this. Take care if you specify JPG or PNG when you do
104105
not know the formats of the source images, as this could lead to unwanted results such as converting JPEGs to PNGs. Specifying
105-
both PNG and JPG is not supported and will be ignored.
106+
both PNG and JPG is not supported and will be ignored. AVIF support is currently experimental.
106107
`,
107108
defaultValue: [`auto`, `webp`],
108109
},

packages/gatsby-plugin-manifest/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"gatsby-core-utils": "^1.8.0-next.0",
1212
"gatsby-plugin-utils": "^0.7.0-next.1",
1313
"semver": "^7.3.2",
14-
"sharp": "^0.26.3"
14+
"sharp": "^0.27.0"
1515
},
1616
"devDependencies": {
1717
"@babel/cli": "^7.12.1",

packages/gatsby-plugin-sharp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"probe-image-size": "^5.0.0",
2424
"progress": "^2.0.3",
2525
"semver": "^7.3.4",
26-
"sharp": "^0.26.3",
26+
"sharp": "^0.27.0",
2727
"svgo": "1.3.2",
2828
"uuid": "3.4.0"
2929
},

packages/gatsby-plugin-sharp/src/image-data.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { createTransformObject } from "./plugin-options"
99

1010
const DEFAULT_BLURRED_IMAGE_WIDTH = 20
1111

12-
type ImageFormat = "jpg" | "png" | "webp" | "" | "auto"
12+
type ImageFormat = "jpg" | "png" | "webp" | "avif" | "" | "auto"
1313
export interface ISharpGatsbyImageArgs {
1414
layout?: "fixed" | "fluid" | "constrained"
1515
formats?: Array<ImageFormat>
@@ -28,6 +28,7 @@ export interface ISharpGatsbyImageArgs {
2828
jpgOptions: Record<string, unknown>
2929
pngOptions: Record<string, unknown>
3030
webpOptions: Record<string, unknown>
31+
avifOptions: Record<string, unknown>
3132
blurredOptions: { width?: number; toFormat?: ImageFormat }
3233
}
3334
export type FileNode = Node & {
@@ -159,6 +160,12 @@ export async function generateImageData({
159160
} else if (formats.has(`webp`)) {
160161
primaryFormat = `webp`
161162
options = args.webpOptions
163+
} else if (formats.has(`avif`)) {
164+
reporter.warn(
165+
`Image ${file.relativePath} specified only AVIF format, with no fallback format. This will mean your site cannot be viewed in all browsers.`
166+
)
167+
primaryFormat = `avif`
168+
options = args.webpOptions
162169
}
163170

164171
const imageSizes: {
@@ -232,6 +239,35 @@ export async function generateImageData({
232239
},
233240
}
234241

242+
if (primaryFormat !== `avif` && formats.has(`avif`)) {
243+
const transforms = imageSizes.sizes.map(outputWidth => {
244+
const width = Math.round(outputWidth)
245+
const transform = createTransformObject({
246+
quality,
247+
...transformOptions,
248+
fit,
249+
cropFocus,
250+
...args.avifOptions,
251+
width,
252+
height: Math.round(width / imageSizes.aspectRatio),
253+
toFormat: `avif`,
254+
})
255+
return transform
256+
})
257+
258+
const avifImages = batchQueueImageResizing({
259+
file,
260+
transforms,
261+
reporter,
262+
})
263+
264+
imageProps.images.sources?.push({
265+
srcSet: getSrcSet(avifImages),
266+
type: `image/avif`,
267+
sizes,
268+
})
269+
}
270+
235271
if (primaryFormat !== `webp` && formats.has(`webp`)) {
236272
const transforms = imageSizes.sizes.map(outputWidth => {
237273
const width = Math.round(outputWidth)
@@ -253,10 +289,9 @@ export async function generateImageData({
253289
transforms,
254290
reporter,
255291
})
256-
const webpSrcSet = getSrcSet(webpImages)
257292

258293
imageProps.images.sources?.push({
259-
srcSet: webpSrcSet,
294+
srcSet: getSrcSet(webpImages),
260295
type: `image/webp`,
261296
sizes,
262297
})

packages/gatsby-remark-images-contentful/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"is-relative-url": "^3.0.0",
2323
"lodash": "^4.17.20",
2424
"semver": "^7.3.2",
25-
"sharp": "^0.26.3",
25+
"sharp": "^0.27.0",
2626
"unist-util-select": "^1.5.0"
2727
},
2828
"devDependencies": {

packages/gatsby-transformer-sharp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"potrace": "^2.1.8",
1414
"probe-image-size": "^5.0.0",
1515
"semver": "^7.3.4",
16-
"sharp": "^0.26.3"
16+
"sharp": "^0.27.0"
1717
},
1818
"devDependencies": {
1919
"@babel/cli": "^7.12.1",

packages/gatsby-transformer-sharp/src/customize-schema.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const {
3939
WebPOptionsType,
4040
BlurredOptionsType,
4141
TransformOptionsType,
42+
AVIFOptionsType,
4243
} = require(`./types`)
4344
const { stripIndent } = require(`common-tags`)
4445
const { prefixId, CODES } = require(`./error-utils`)
@@ -493,6 +494,10 @@ const imageNodeType = ({
493494
type: WebPOptionsType,
494495
description: `Options to pass to sharp when generating WebP images.`,
495496
},
497+
avifOptions: {
498+
type: AVIFOptionsType,
499+
description: `Options to pass to sharp when generating AVIF images.`,
500+
},
496501
transformOptions: {
497502
type: TransformOptionsType,
498503
description: `Options to pass to sharp to control cropping and other image manipulations.`,

packages/gatsby-transformer-sharp/src/types.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const ImageFormatType = new GraphQLEnumType({
2222
JPG: { value: `jpg` },
2323
PNG: { value: `png` },
2424
WEBP: { value: `webp` },
25+
AVIF: { value: `avif` },
2526
},
2627
})
2728

@@ -129,6 +130,23 @@ export const WebPOptionsType = new GraphQLInputObjectType({
129130
},
130131
})
131132

133+
export const AVIFOptionsType = new GraphQLInputObjectType({
134+
name: `AVIFOptions`,
135+
fields: (): GraphQLInputFieldConfigMap => {
136+
return {
137+
quality: {
138+
type: GraphQLInt,
139+
},
140+
lossless: {
141+
type: GraphQLBoolean,
142+
},
143+
speed: {
144+
type: GraphQLInt,
145+
},
146+
}
147+
},
148+
})
149+
132150
export const DuotoneGradientType = new GraphQLInputObjectType({
133151
name: `DuotoneGradient`,
134152
fields: (): GraphQLInputFieldConfigMap => {

yarn.lock

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17524,10 +17524,10 @@ node-abi@^2.7.0:
1752417524
dependencies:
1752517525
semver "^5.4.1"
1752617526

17527-
node-addon-api@^3.0.2:
17528-
version "3.0.2"
17529-
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.2.tgz#04bc7b83fd845ba785bb6eae25bc857e1ef75681"
17530-
integrity sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==
17527+
node-addon-api@^3.1.0:
17528+
version "3.1.0"
17529+
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239"
17530+
integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==
1753117531

1753217532
node-cleanup@^2.1.2:
1753317533
version "2.1.2"
@@ -22782,18 +22782,18 @@ shallowequal@^1.1.0:
2278222782
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
2278322783
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
2278422784

22785-
sharp@^0.26.3:
22786-
version "0.26.3"
22787-
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.26.3.tgz#9de8577a986b22538e6e12ced1f7e8a53f9728de"
22788-
integrity sha512-NdEJ9S6AMr8Px0zgtFo1TJjMK/ROMU92MkDtYn2BBrDjIx3YfH9TUyGdzPC+I/L619GeYQc690Vbaxc5FPCCWg==
22785+
sharp@^0.27.0:
22786+
version "0.27.0"
22787+
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.27.0.tgz#523fba913ba674985dcc688a6a5237384079d80f"
22788+
integrity sha512-II+YBCW3JuVWQZdpTEA2IBjJcYXPuoKo3AUqYuW+FK9Um93v2gPE2ihICCsN5nHTUoP8WCjqA83c096e8n//Rw==
2278922789
dependencies:
2279022790
array-flatten "^3.0.0"
2279122791
color "^3.1.3"
2279222792
detect-libc "^1.0.3"
22793-
node-addon-api "^3.0.2"
22793+
node-addon-api "^3.1.0"
2279422794
npmlog "^4.1.2"
2279522795
prebuild-install "^6.0.0"
22796-
semver "^7.3.2"
22796+
semver "^7.3.4"
2279722797
simple-get "^4.0.0"
2279822798
tar-fs "^2.1.1"
2279922799
tunnel-agent "^0.6.0"

0 commit comments

Comments
 (0)