Skip to content

Commit

Permalink
feat(gatsby-source-contentful): Add gatsbyImageData resolver (#28236)
Browse files Browse the repository at this point in the history
* 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 <mathews.kyle+gatsbybot@gmail.com>
Co-authored-by: Lennart <lekoarts@gmail.com>

* chore(docs): Add cassandra to list of database sources (#29137)

Co-authored-by: Lennart <lekoarts@gmail.com>

* 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 <matt@gatsbyjs.com>

* 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 <63111210+nathanchu@users.noreply.github.com>
Co-authored-by: Himanshu Bisht <32536536+Himanshu-27@users.noreply.github.com>
Co-authored-by: Yuki Takemoto <mottox2@users.noreply.github.com>
Co-authored-by: yonatanLehman <32838532+yonatanLehman@users.noreply.github.com>
Co-authored-by: gatsbybot <mathews.kyle+gatsbybot@gmail.com>
Co-authored-by: Lennart <lekoarts@gmail.com>
Co-authored-by: Alex Leventer <alexleventer@gmail.com>
Co-authored-by: Benedikt Rötsch <opensource@axe312.dev>
Co-authored-by: Benedikt Rötsch <axe312ger@users.noreply.github.com>
  • Loading branch information
10 people committed Jan 28, 2021
1 parent c2f0298 commit a823622
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 16 deletions.
3 changes: 2 additions & 1 deletion packages/gatsby-plugin-sharp/src/index.js
Expand Up @@ -20,7 +20,7 @@ const { memoizedTraceSVG, notMemoizedtraceSVG } = require(`./trace-svg`)
const duotone = require(`./duotone`)
const { IMAGE_PROCESSING_JOB_NAME } = require(`./gatsby-worker`)
const { getDimensionsAndAspectRatio } = require(`./utils`)
// const { rgbToHex } = require(`./utils`)
const { getDominantColor } = require(`./utils`)

const imageSizeCache = new Map()

Expand Down Expand Up @@ -777,6 +777,7 @@ exports.fluid = fluid
exports.fixed = fixed
exports.getImageSize = getImageSize
exports.getImageSizeAsync = getImageSizeAsync
exports.getDominantColor = getDominantColor
exports.stats = stats
exports._unstable_createJob = createJob
exports._lazyJobsEnabled = lazyJobsEnabled
21 changes: 21 additions & 0 deletions packages/gatsby-plugin-sharp/src/utils.js
Expand Up @@ -361,3 +361,24 @@ export function getDimensionsAndAspectRatio(dimensions, options) {
aspectRatio: width / height,
}
}

const dominantColorCache = new Map()

export const getDominantColor = async absolutePath => {
let dominantColor = dominantColorCache.get(absolutePath)
if (dominantColor) {
return dominantColor
}

const pipeline = sharp(absolutePath)
const { dominant } = await pipeline.stats()

// Fallback in case sharp doesn't support dominant
dominantColor = dominant
? rgbToHex(dominant.r, dominant.g, dominant.b)
: `rgba(0,0,0,0.5)`

dominantColorCache.set(absolutePath, dominantColor)

return dominantColor
}
4 changes: 3 additions & 1 deletion packages/gatsby-source-contentful/package.json
Expand Up @@ -16,6 +16,7 @@
"contentful": "^7.14.12",
"fs-extra": "^9.0.1",
"gatsby-core-utils": "^1.10.0-next.0",
"gatsby-plugin-image": "^0.7.0-next.0",
"gatsby-plugin-utils": "^0.9.0-next.0",
"gatsby-source-filesystem": "^2.11.0-next.0",
"is-online": "^8.5.1",
Expand All @@ -40,7 +41,8 @@
"license": "MIT",
"peerDependencies": {
"gatsby": "^2.12.1",
"gatsby-plugin-sharp": "^2.6.14"
"gatsby-plugin-sharp": "^2.6.14",
"sharp": "^0.26.0"
},
"repository": {
"type": "git",
Expand Down
171 changes: 157 additions & 14 deletions packages/gatsby-source-contentful/src/extend-node-type.js
@@ -1,7 +1,9 @@
// @ts-check
const fs = require(`fs`)
const path = require(`path`)
const crypto = require(`crypto`)

const sortBy = require(`lodash/sortBy`)
const axios = require(`axios`)
const {
GraphQLObjectType,
Expand All @@ -12,6 +14,11 @@ const {
GraphQLNonNull,
} = require(`gatsby/graphql`)
const qs = require(`qs`)
const { generateImageData } = require(`gatsby-plugin-image`)
const {
getGatsbyImageFieldConfig,
} = require(`gatsby-plugin-image/graphql-utils`)
const { stripIndent } = require(`common-tags`)

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

Expand Down Expand Up @@ -157,7 +164,10 @@ const createUrl = (imgUrl, options = {}) => {
const urlArgs = {
w: options.width || undefined,
h: options.height || undefined,
fl: options.jpegProgressive ? `progressive` : undefined,
fl:
options.toFormat === `jpg` && options.jpegProgressive
? `progressive`
: undefined,
q: options.quality || undefined,
fm: options.toFormat || undefined,
fit: options.resizingBehavior || undefined,
Expand All @@ -170,6 +180,37 @@ const createUrl = (imgUrl, options = {}) => {
}
exports.createUrl = createUrl

const generateImageSource = (
filename,
width,
height,
toFormat,
_fit, // We use resizingBehavior instead
{ jpegProgressive, quality, cropFocus, backgroundColor, resizingBehavior }
) => {
const src = createUrl(filename, {
width,
height,
toFormat,
resizingBehavior,
background: backgroundColor?.replace(`#`, `rgb:`),
quality,
jpegProgressive,
cropFocus,
})
return { width, height, format: toFormat, src }
}

exports.generateImageSource = generateImageSource

const fitMap = new Map([
[`pad`, `contain`],
[`fill`, `cover`],
[`scale`, `fill`],
[`crop`, `cover`],
[`thumb`, `cover`],
])

const resolveFixed = (image, options) => {
if (!isImage(image)) return null

Expand Down Expand Up @@ -223,8 +264,11 @@ const resolveFixed = (image, options) => {
)
})

// Sort sizes for prettiness.
const sortedSizes = sortBy(filteredSizes)

// Create the srcSet.
const srcSet = filteredSizes
const srcSet = sortedSizes
.map((size, i) => {
let resolution
switch (i) {
Expand Down Expand Up @@ -328,17 +372,19 @@ const resolveFluid = (image, options) => {

// Add the original image (if it isn't already in there) to ensure the largest image possible
// is available for small images.
const pwidth = parseInt(width, 10)
if (
!filteredSizes.includes(pwidth) &&
pwidth < CONTENTFUL_IMAGE_MAX_SIZE &&
Math.round(pwidth / desiredAspectRatio) < CONTENTFUL_IMAGE_MAX_SIZE
!filteredSizes.includes(width) &&
width < CONTENTFUL_IMAGE_MAX_SIZE &&
Math.round(width / desiredAspectRatio) < CONTENTFUL_IMAGE_MAX_SIZE
) {
filteredSizes.push(pwidth)
filteredSizes.push(width)
}

// Sort sizes for prettiness.
const sortedSizes = sortBy(filteredSizes)

// Create the srcSet.
const srcSet = filteredSizes
const srcSet = sortedSizes
.map(width => {
const h = Math.round(width / desiredAspectRatio)
return `${createUrl(image.file.url, {
Expand Down Expand Up @@ -423,7 +469,7 @@ const fixedNodeType = ({ name, getTracedSVG }) => {
srcSet: { type: new GraphQLNonNull(GraphQLString) },
srcWebp: {
type: GraphQLString,
resolve({ image, options, context }) {
resolve({ image, options }) {
if (
image?.file?.contentType === `image/webp` ||
options.toFormat === `webp`
Expand All @@ -440,7 +486,7 @@ const fixedNodeType = ({ name, getTracedSVG }) => {
},
srcSetWebp: {
type: GraphQLString,
resolve({ image, options, context }) {
resolve({ image, options }) {
if (
image?.file?.contentType === `image/webp` ||
options.toFormat === `webp`
Expand Down Expand Up @@ -516,7 +562,7 @@ const fluidNodeType = ({ name, getTracedSVG }) => {
srcSet: { type: new GraphQLNonNull(GraphQLString) },
srcWebp: {
type: GraphQLString,
resolve({ image, options, context }) {
resolve({ image, options }) {
if (
image?.file?.contentType === `image/webp` ||
options.toFormat === `webp`
Expand All @@ -533,7 +579,7 @@ const fluidNodeType = ({ name, getTracedSVG }) => {
},
srcSetWebp: {
type: GraphQLString,
resolve({ image, options, context }) {
resolve({ image, options }) {
if (
image?.file?.contentType === `image/webp` ||
options.toFormat === `webp`
Expand Down Expand Up @@ -595,7 +641,9 @@ const fluidNodeType = ({ name, getTracedSVG }) => {
}
}

exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
let warnedForBeta = false

exports.extendNodeType = ({ type, store, reporter }) => {
if (type.name !== `ContentfulAsset`) {
return {}
}
Expand Down Expand Up @@ -627,6 +675,69 @@ exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
})
}

const getDominantColor = async ({ image, options }) => {
try {
const absolutePath = await cacheImage(store, image, options)

const pluginSharp = require(`gatsby-plugin-sharp`)
if (!(`getDominantColor` in pluginSharp)) {
console.error(
`[gatsby-source-contentful] Please upgrade gatsby-plugin-sharp`
)
return `rgba(0,0,0,0.5)`
}

return pluginSharp.getDominantColor(absolutePath)
} catch (e) {
console.error(
`[gatsby-source-contentful] Please install gatsby-plugin-sharp`
)
return `rgba(0,0,0,0.5)`
}
}

const resolveGatsbyImageData = async (image, options) => {
const { baseUrl, ...sourceMetadata } = getBasicImageProps(image, options)

const imageProps = generateImageData({
...options,
pluginName: `gatsby-source-contentful`,
sourceMetadata,
filename: baseUrl,
generateImageSource,
fit: fitMap.get(options.resizingBehavior),
options,
})

let placeholderDataURI = null

if (options.placeholder === `dominantColor`) {
imageProps.backgroundColor = await getDominantColor({
image,
options,
})
}

if (options.placeholder === `blurred`) {
placeholderDataURI = await getBase64Image({
baseUrl,
})
}

if (options.placeholder === `tracedSVG`) {
placeholderDataURI = await getTracedSVG({
image,
options,
})
}

if (placeholderDataURI) {
imageProps.placeholder = { fallback: placeholderDataURI }
}

return imageProps
}

// TODO: Remove resolutionsNode and sizesNode for Gatsby v3
const fixedNode = fixedNodeType({ name: `ContentfulFixed`, getTracedSVG })
const resolutionsNode = fixedNodeType({
Expand All @@ -639,11 +750,43 @@ exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
const sizesNode = fluidNodeType({ name: `ContentfulSizes`, getTracedSVG })
sizesNode.deprecationReason = `Sizes was deprecated in Gatsby v2. It's been renamed to "fluid" https://example.com/write-docs-and-fix-this-example-link`

// gatsby-plugin-image
const getGatsbyImageData = () => {
if (!warnedForBeta) {
reporter.warn(
stripIndent`
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`
)
warnedForBeta = true
}

return getGatsbyImageFieldConfig(resolveGatsbyImageData, {
jpegProgressive: {
type: GraphQLBoolean,
defaultValue: true,
},
resizingBehavior: {
type: ImageResizingBehavior,
},
cropFocus: {
type: ImageCropFocusType,
},
quality: {
type: GraphQLInt,
defaultValue: 50,
},
backgroundColor: {
type: GraphQLString,
},
})
}

return {
fixed: fixedNode,
resolutions: resolutionsNode,
fluid: fluidNode,
sizes: sizesNode,
gatsbyImageData: getGatsbyImageData(),
resize: {
type: new GraphQLObjectType({
name: `ContentfulResize`,
Expand Down Expand Up @@ -693,7 +836,7 @@ exports.extendNodeType = ({ type, store, cache, getNodesByType }) => {
defaultValue: null,
},
},
resolve(image, options, context) {
resolve(image, options) {
return resolveResize(image, options)
},
},
Expand Down

0 comments on commit a823622

Please sign in to comment.