Skip to content

Commit a823622

Browse files
ascorbicnathanchuHimanshu-27mottox2yonatanLehman
authored
feat(gatsby-source-contentful): Add gatsbyImageData resolver (#28236)
* Add types for resolver utils * Fix * fix(gatsby-plugin-styled-components): add `namespace` option (#29095) * chore(docs): Netlify CMS added branch to backend settings (#29162) * docs: fix broken link (#29163) * chore(docs): Update debugging-the-build-process (#29067) Co-authored-by: gatsbybot <[email protected]> Co-authored-by: Lennart <[email protected]> * chore(docs): Add cassandra to list of database sources (#29137) Co-authored-by: Lennart <[email protected]> * feat(contentful): add support for gatsby-plugin-image * make gatsby-plugin-image a dependency again and warn users about the beta feature * WIP - support traced svgs again * Update packages/gatsby-source-contentful/src/extend-node-type.js Co-authored-by: Matt Kane <[email protected]> * fix: set progressive jpg parameter only when format is forced to jpg * feat: add support for contentful backgorund parameter * feat: add support for dominant color placeholder * Error handling, and update api * Use helper * Remove gratuitous parseInt on a number * Destructured import Co-authored-by: Nathan Chu <[email protected]> Co-authored-by: Himanshu Bisht <[email protected]> Co-authored-by: Yuki Takemoto <[email protected]> Co-authored-by: yonatanLehman <[email protected]> Co-authored-by: gatsbybot <[email protected]> Co-authored-by: Lennart <[email protected]> Co-authored-by: Alex Leventer <[email protected]> Co-authored-by: Benedikt Rötsch <[email protected]> Co-authored-by: Benedikt Rötsch <[email protected]>
1 parent c2f0298 commit a823622

File tree

4 files changed

+183
-16
lines changed

4 files changed

+183
-16
lines changed

packages/gatsby-plugin-sharp/src/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const { memoizedTraceSVG, notMemoizedtraceSVG } = require(`./trace-svg`)
2020
const duotone = require(`./duotone`)
2121
const { IMAGE_PROCESSING_JOB_NAME } = require(`./gatsby-worker`)
2222
const { getDimensionsAndAspectRatio } = require(`./utils`)
23-
// const { rgbToHex } = require(`./utils`)
23+
const { getDominantColor } = require(`./utils`)
2424

2525
const imageSizeCache = new Map()
2626

@@ -777,6 +777,7 @@ exports.fluid = fluid
777777
exports.fixed = fixed
778778
exports.getImageSize = getImageSize
779779
exports.getImageSizeAsync = getImageSizeAsync
780+
exports.getDominantColor = getDominantColor
780781
exports.stats = stats
781782
exports._unstable_createJob = createJob
782783
exports._lazyJobsEnabled = lazyJobsEnabled

packages/gatsby-plugin-sharp/src/utils.js

+21
Original file line numberDiff line numberDiff line change
@@ -361,3 +361,24 @@ export function getDimensionsAndAspectRatio(dimensions, options) {
361361
aspectRatio: width / height,
362362
}
363363
}
364+
365+
const dominantColorCache = new Map()
366+
367+
export const getDominantColor = async absolutePath => {
368+
let dominantColor = dominantColorCache.get(absolutePath)
369+
if (dominantColor) {
370+
return dominantColor
371+
}
372+
373+
const pipeline = sharp(absolutePath)
374+
const { dominant } = await pipeline.stats()
375+
376+
// Fallback in case sharp doesn't support dominant
377+
dominantColor = dominant
378+
? rgbToHex(dominant.r, dominant.g, dominant.b)
379+
: `rgba(0,0,0,0.5)`
380+
381+
dominantColorCache.set(absolutePath, dominantColor)
382+
383+
return dominantColor
384+
}

packages/gatsby-source-contentful/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"contentful": "^7.14.12",
1717
"fs-extra": "^9.0.1",
1818
"gatsby-core-utils": "^1.10.0-next.0",
19+
"gatsby-plugin-image": "^0.7.0-next.0",
1920
"gatsby-plugin-utils": "^0.9.0-next.0",
2021
"gatsby-source-filesystem": "^2.11.0-next.0",
2122
"is-online": "^8.5.1",
@@ -40,7 +41,8 @@
4041
"license": "MIT",
4142
"peerDependencies": {
4243
"gatsby": "^2.12.1",
43-
"gatsby-plugin-sharp": "^2.6.14"
44+
"gatsby-plugin-sharp": "^2.6.14",
45+
"sharp": "^0.26.0"
4446
},
4547
"repository": {
4648
"type": "git",

packages/gatsby-source-contentful/src/extend-node-type.js

+157-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
// @ts-check
12
const fs = require(`fs`)
23
const path = require(`path`)
34
const crypto = require(`crypto`)
45

6+
const sortBy = require(`lodash/sortBy`)
57
const axios = require(`axios`)
68
const {
79
GraphQLObjectType,
@@ -12,6 +14,11 @@ const {
1214
GraphQLNonNull,
1315
} = require(`gatsby/graphql`)
1416
const qs = require(`qs`)
17+
const { generateImageData } = require(`gatsby-plugin-image`)
18+
const {
19+
getGatsbyImageFieldConfig,
20+
} = require(`gatsby-plugin-image/graphql-utils`)
21+
const { stripIndent } = require(`common-tags`)
1522

1623
const cacheImage = require(`./cache-image`)
1724

@@ -157,7 +164,10 @@ const createUrl = (imgUrl, options = {}) => {
157164
const urlArgs = {
158165
w: options.width || undefined,
159166
h: options.height || undefined,
160-
fl: options.jpegProgressive ? `progressive` : undefined,
167+
fl:
168+
options.toFormat === `jpg` && options.jpegProgressive
169+
? `progressive`
170+
: undefined,
161171
q: options.quality || undefined,
162172
fm: options.toFormat || undefined,
163173
fit: options.resizingBehavior || undefined,
@@ -170,6 +180,37 @@ const createUrl = (imgUrl, options = {}) => {
170180
}
171181
exports.createUrl = createUrl
172182

183+
const generateImageSource = (
184+
filename,
185+
width,
186+
height,
187+
toFormat,
188+
_fit, // We use resizingBehavior instead
189+
{ jpegProgressive, quality, cropFocus, backgroundColor, resizingBehavior }
190+
) => {
191+
const src = createUrl(filename, {
192+
width,
193+
height,
194+
toFormat,
195+
resizingBehavior,
196+
background: backgroundColor?.replace(`#`, `rgb:`),
197+
quality,
198+
jpegProgressive,
199+
cropFocus,
200+
})
201+
return { width, height, format: toFormat, src }
202+
}
203+
204+
exports.generateImageSource = generateImageSource
205+
206+
const fitMap = new Map([
207+
[`pad`, `contain`],
208+
[`fill`, `cover`],
209+
[`scale`, `fill`],
210+
[`crop`, `cover`],
211+
[`thumb`, `cover`],
212+
])
213+
173214
const resolveFixed = (image, options) => {
174215
if (!isImage(image)) return null
175216

@@ -223,8 +264,11 @@ const resolveFixed = (image, options) => {
223264
)
224265
})
225266

267+
// Sort sizes for prettiness.
268+
const sortedSizes = sortBy(filteredSizes)
269+
226270
// Create the srcSet.
227-
const srcSet = filteredSizes
271+
const srcSet = sortedSizes
228272
.map((size, i) => {
229273
let resolution
230274
switch (i) {
@@ -328,17 +372,19 @@ const resolveFluid = (image, options) => {
328372

329373
// Add the original image (if it isn't already in there) to ensure the largest image possible
330374
// is available for small images.
331-
const pwidth = parseInt(width, 10)
332375
if (
333-
!filteredSizes.includes(pwidth) &&
334-
pwidth < CONTENTFUL_IMAGE_MAX_SIZE &&
335-
Math.round(pwidth / desiredAspectRatio) < CONTENTFUL_IMAGE_MAX_SIZE
376+
!filteredSizes.includes(width) &&
377+
width < CONTENTFUL_IMAGE_MAX_SIZE &&
378+
Math.round(width / desiredAspectRatio) < CONTENTFUL_IMAGE_MAX_SIZE
336379
) {
337-
filteredSizes.push(pwidth)
380+
filteredSizes.push(width)
338381
}
339382

383+
// Sort sizes for prettiness.
384+
const sortedSizes = sortBy(filteredSizes)
385+
340386
// Create the srcSet.
341-
const srcSet = filteredSizes
387+
const srcSet = sortedSizes
342388
.map(width => {
343389
const h = Math.round(width / desiredAspectRatio)
344390
return `${createUrl(image.file.url, {
@@ -423,7 +469,7 @@ const fixedNodeType = ({ name, getTracedSVG }) => {
423469
srcSet: { type: new GraphQLNonNull(GraphQLString) },
424470
srcWebp: {
425471
type: GraphQLString,
426-
resolve({ image, options, context }) {
472+
resolve({ image, options }) {
427473
if (
428474
image?.file?.contentType === `image/webp` ||
429475
options.toFormat === `webp`
@@ -440,7 +486,7 @@ const fixedNodeType = ({ name, getTracedSVG }) => {
440486
},
441487
srcSetWebp: {
442488
type: GraphQLString,
443-
resolve({ image, options, context }) {
489+
resolve({ image, options }) {
444490
if (
445491
image?.file?.contentType === `image/webp` ||
446492
options.toFormat === `webp`
@@ -516,7 +562,7 @@ const fluidNodeType = ({ name, getTracedSVG }) => {
516562
srcSet: { type: new GraphQLNonNull(GraphQLString) },
517563
srcWebp: {
518564
type: GraphQLString,
519-
resolve({ image, options, context }) {
565+
resolve({ image, options }) {
520566
if (
521567
image?.file?.contentType === `image/webp` ||
522568
options.toFormat === `webp`
@@ -533,7 +579,7 @@ const fluidNodeType = ({ name, getTracedSVG }) => {
533579
},
534580
srcSetWebp: {
535581
type: GraphQLString,
536-
resolve({ image, options, context }) {
582+
resolve({ image, options }) {
537583
if (
538584
image?.file?.contentType === `image/webp` ||
539585
options.toFormat === `webp`
@@ -595,7 +641,9 @@ const fluidNodeType = ({ name, getTracedSVG }) => {
595641
}
596642
}
597643

598-
exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
644+
let warnedForBeta = false
645+
646+
exports.extendNodeType = ({ type, store, reporter }) => {
599647
if (type.name !== `ContentfulAsset`) {
600648
return {}
601649
}
@@ -627,6 +675,69 @@ exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
627675
})
628676
}
629677

678+
const getDominantColor = async ({ image, options }) => {
679+
try {
680+
const absolutePath = await cacheImage(store, image, options)
681+
682+
const pluginSharp = require(`gatsby-plugin-sharp`)
683+
if (!(`getDominantColor` in pluginSharp)) {
684+
console.error(
685+
`[gatsby-source-contentful] Please upgrade gatsby-plugin-sharp`
686+
)
687+
return `rgba(0,0,0,0.5)`
688+
}
689+
690+
return pluginSharp.getDominantColor(absolutePath)
691+
} catch (e) {
692+
console.error(
693+
`[gatsby-source-contentful] Please install gatsby-plugin-sharp`
694+
)
695+
return `rgba(0,0,0,0.5)`
696+
}
697+
}
698+
699+
const resolveGatsbyImageData = async (image, options) => {
700+
const { baseUrl, ...sourceMetadata } = getBasicImageProps(image, options)
701+
702+
const imageProps = generateImageData({
703+
...options,
704+
pluginName: `gatsby-source-contentful`,
705+
sourceMetadata,
706+
filename: baseUrl,
707+
generateImageSource,
708+
fit: fitMap.get(options.resizingBehavior),
709+
options,
710+
})
711+
712+
let placeholderDataURI = null
713+
714+
if (options.placeholder === `dominantColor`) {
715+
imageProps.backgroundColor = await getDominantColor({
716+
image,
717+
options,
718+
})
719+
}
720+
721+
if (options.placeholder === `blurred`) {
722+
placeholderDataURI = await getBase64Image({
723+
baseUrl,
724+
})
725+
}
726+
727+
if (options.placeholder === `tracedSVG`) {
728+
placeholderDataURI = await getTracedSVG({
729+
image,
730+
options,
731+
})
732+
}
733+
734+
if (placeholderDataURI) {
735+
imageProps.placeholder = { fallback: placeholderDataURI }
736+
}
737+
738+
return imageProps
739+
}
740+
630741
// TODO: Remove resolutionsNode and sizesNode for Gatsby v3
631742
const fixedNode = fixedNodeType({ name: `ContentfulFixed`, getTracedSVG })
632743
const resolutionsNode = fixedNodeType({
@@ -639,11 +750,43 @@ exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
639750
const sizesNode = fluidNodeType({ name: `ContentfulSizes`, getTracedSVG })
640751
sizesNode.deprecationReason = `Sizes was deprecated in Gatsby v2. It's been renamed to "fluid" https://example.com/write-docs-and-fix-this-example-link`
641752

753+
// gatsby-plugin-image
754+
const getGatsbyImageData = () => {
755+
if (!warnedForBeta) {
756+
reporter.warn(
757+
stripIndent`
758+
Thank you for trying the beta version of the \`gatsbyImageData\` API. Please provide feedback and report any issues at: https://github.com/gatsbyjs/gatsby/discussions/27950`
759+
)
760+
warnedForBeta = true
761+
}
762+
763+
return getGatsbyImageFieldConfig(resolveGatsbyImageData, {
764+
jpegProgressive: {
765+
type: GraphQLBoolean,
766+
defaultValue: true,
767+
},
768+
resizingBehavior: {
769+
type: ImageResizingBehavior,
770+
},
771+
cropFocus: {
772+
type: ImageCropFocusType,
773+
},
774+
quality: {
775+
type: GraphQLInt,
776+
defaultValue: 50,
777+
},
778+
backgroundColor: {
779+
type: GraphQLString,
780+
},
781+
})
782+
}
783+
642784
return {
643785
fixed: fixedNode,
644786
resolutions: resolutionsNode,
645787
fluid: fluidNode,
646788
sizes: sizesNode,
789+
gatsbyImageData: getGatsbyImageData(),
647790
resize: {
648791
type: new GraphQLObjectType({
649792
name: `ContentfulResize`,
@@ -693,7 +836,7 @@ exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
693836
defaultValue: null,
694837
},
695838
},
696-
resolve(image, options, context) {
839+
resolve(image, options) {
697840
return resolveResize(image, options)
698841
},
699842
},

0 commit comments

Comments
 (0)